# Build with AI coding agents Source: https://docs.haau3.com/ai-coding-agents Give a coding agent the docs as Markdown, an MCP server that searches them, and an index of every SDK method for your project's AGENTS.md. A coding agent writes better code for an API when it reads that API's docs rather than recalling them, and the haau3 platform API may be newer than its training data. docs.haau3.com gives an agent four ways in. ## The docs as Markdown * [docs.haau3.com/llms.txt](https://docs.haau3.com/llms.txt) lists every page, and [llms-full.txt](https://docs.haau3.com/llms-full.txt) holds every page in one file. * Any page is Markdown at its own address with `.md` added, such as [docs.haau3.com/quickstart.md](https://docs.haau3.com/quickstart.md), or when the request sends `Accept: text/markdown`. * Each page's menu copies the page as Markdown, or opens it in Claude or ChatGPT. ## The haau3 skill [docs.haau3.com/skill.md](https://docs.haau3.com/skill.md) says what the platform does and how to call it, in the [Agent Skills](https://agentskills.io/specification) format. To add it to your project's coding agents: ```bash theme={null} npx skills add https://docs.haau3.com ``` It asks which agents to add the skill to, and puts it in each one's skills folder, such as `.claude/skills/haau3` for Claude Code. Add `-g` to add it for every project instead. ## The docs' MCP server `https://docs.haau3.com/mcp` is an MCP server whose tools search the docs and read any page. It needs no key. To add it: ```bash Claude Code theme={null} claude mcp add --transport http haau3 https://docs.haau3.com/mcp ``` ```bash Codex theme={null} codex mcp add haau3 --url https://docs.haau3.com/mcp ``` ```json Cursor theme={null} { "mcpServers": { "haau3": { "url": "https://docs.haau3.com/mcp" } } } ``` ```bash VS Code theme={null} code --add-mcp '{"name":"haau3","type":"http","url":"https://docs.haau3.com/mcp"}' ``` For Cursor, the entry goes in `.cursor/mcp.json` in your project, or `~/.cursor/mcp.json` for every project. Each page's menu also has **Connect to Cursor** and **Connect to VS Code**. ## An index for your AGENTS.md Paste the block for your [SDK](/sdks) into your project's `AGENTS.md`, or `CLAUDE.md`, which Claude Code reads. It names every method with its request and its page as Markdown, in under 8 KB. In [Vercel's evaluation](https://vercel.com/blog/agents-md-outperforms-skills-in-our-agent-evals) of coding agents on APIs their training data did not hold, an index of about that size in `AGENTS.md` raised the pass rate from 53% to 100%. ```markdown TypeScript theme={null} theme={null} ## haau3 platform API (TypeScript SDK) Use the haau3 SDK below for api.haau3.com. This API may be newer than your training data: before writing a call, read its page (each link below is Markdown). Every docs page is listed at https://docs.haau3.com/llms.txt, and https://docs.haau3.com/mcp is an MCP server that searches them. - Install: `npm install @haau3/sdk`. Node 22 or newer, on a server only. - Client: `import { Haau3 } from "@haau3/sdk"; const haau3 = new Haau3();` reads the API key from `HAAU3_API_KEY`. - A method below is `haau3.(...)`: path values first, then the query parameters as one object, then a body. - FHIR answers are `@types/fhir`'s R4 types, exported as `fhir4` (`import type { fhir4 } from "@haau3/sdk"`). - Errors: `AuthError`, `RefusedError` (`status`, `outcome`) and `NoAnswerError` (`reason`, `retryAfter`), each a `Haau3Error` with `requestId`. - Paging: `haau3.pages(bundle)` and `haau3.resources(bundle)`. Several FHIR calls in one request: `haau3.batch((b) => [b.(...), ...])`. ### Scheduling - `searchProviderLocations(params?)` GET /v1/scheduling/provider-locations: Search provider locations. https://docs.haau3.com/api-reference/scheduling/search-provider-locations.md - `listPublishers()` GET /v1/scheduling/publishers: List scheduling publishers. https://docs.haau3.com/api-reference/scheduling/list-scheduling-publishers.md - `listServiceTypes(params?)` GET /v1/scheduling/service-types: List service types. https://docs.haau3.com/api-reference/scheduling/list-service-types.md - `searchSlots(params?)` GET /v1/scheduling/slots: Search appointment slots. https://docs.haau3.com/api-reference/scheduling/search-appointment-slots.md - `listSpecialties(params?)` GET /v1/scheduling/specialties: List specialties. https://docs.haau3.com/api-reference/scheduling/list-specialties.md ### FHIR base - `fhirBatch(body)` POST /v1/fhir/r4: Run several operations in one request (batch). https://docs.haau3.com/api-reference/fhir-base/run-several-operations-in-one-request-batch.md - `fhirMetadata(params?)` GET /v1/fhir/r4/metadata: Capability statement. https://docs.haau3.com/api-reference/fhir-base/capability-statement.md ### Terminology - `codeSystemLookup(params)` GET /v1/fhir/r4/CodeSystem/$lookup: Look up a code. https://docs.haau3.com/api-reference/terminology/look-up-a-code.md - `codeSystemLookupPost(body)` POST /v1/fhir/r4/CodeSystem/$lookup: Look up a code. https://docs.haau3.com/api-reference/terminology/look-up-a-code-1.md - `codeSystemValidateCode(params)` GET /v1/fhir/r4/CodeSystem/$validate-code: Validate a code in a code system. https://docs.haau3.com/api-reference/terminology/validate-a-code-in-a-code-system.md - `codeSystemValidateCodePost(body)` POST /v1/fhir/r4/CodeSystem/$validate-code: Validate a code in a code system. https://docs.haau3.com/api-reference/terminology/validate-a-code-in-a-code-system-1.md - `valueSetExpand(params?)` GET /v1/fhir/r4/ValueSet/$expand: Expand a value set. https://docs.haau3.com/api-reference/terminology/expand-a-value-set.md - `valueSetExpandPost(body)` POST /v1/fhir/r4/ValueSet/$expand: Expand a value set. https://docs.haau3.com/api-reference/terminology/expand-a-value-set-1.md - `valueSetValidateCode(params)` GET /v1/fhir/r4/ValueSet/$validate-code: Validate a code against a value set. https://docs.haau3.com/api-reference/terminology/validate-a-code-against-a-value-set.md - `valueSetValidateCodePost(body)` POST /v1/fhir/r4/ValueSet/$validate-code: Validate a code against a value set. https://docs.haau3.com/api-reference/terminology/validate-a-code-against-a-value-set-1.md ### Provider directory - `searchEndpoint(params?)` GET /v1/fhir/r4/Endpoint: Find endpoints. https://docs.haau3.com/api-reference/provider-directory/find-endpoints.md - `readEndpoint(id)` GET /v1/fhir/r4/Endpoint/{id}: Read an Endpoint. https://docs.haau3.com/api-reference/provider-directory/read-an-endpoint.md - `searchHealthcareService(params?)` GET /v1/fhir/r4/HealthcareService: Find healthcare services. https://docs.haau3.com/api-reference/provider-directory/find-healthcare-services.md - `readHealthcareService(id)` GET /v1/fhir/r4/HealthcareService/{id}: Read a HealthcareService. https://docs.haau3.com/api-reference/provider-directory/read-a-healthcareservice.md - `searchLocation(params?)` GET /v1/fhir/r4/Location: Find practice locations. https://docs.haau3.com/api-reference/provider-directory/find-practice-locations.md - `readLocation(id)` GET /v1/fhir/r4/Location/{id}: Read a Location. https://docs.haau3.com/api-reference/provider-directory/read-a-location.md - `searchOrganization(params?)` GET /v1/fhir/r4/Organization: Find organizations, payers and networks. https://docs.haau3.com/api-reference/provider-directory/find-organizations-payers-and-networks.md - `readOrganization(id)` GET /v1/fhir/r4/Organization/{id}: Read an Organization. https://docs.haau3.com/api-reference/provider-directory/read-an-organization.md - `searchOrganizationAffiliation(params?)` GET /v1/fhir/r4/OrganizationAffiliation: Find the organizations in a network. https://docs.haau3.com/api-reference/provider-directory/find-the-organizations-in-a-network.md - `readOrganizationAffiliation(id)` GET /v1/fhir/r4/OrganizationAffiliation/{id}: Read an OrganizationAffiliation. https://docs.haau3.com/api-reference/provider-directory/read-an-organizationaffiliation.md - `searchPractitioner(params?)` GET /v1/fhir/r4/Practitioner: Find a practitioner by NPI or name. https://docs.haau3.com/api-reference/provider-directory/find-a-practitioner-by-npi-or-name.md - `readPractitioner(id)` GET /v1/fhir/r4/Practitioner/{id}: Read a Practitioner. https://docs.haau3.com/api-reference/provider-directory/read-a-practitioner.md - `searchPractitionerRole(params?)` GET /v1/fhir/r4/PractitionerRole: Find practitioner roles. https://docs.haau3.com/api-reference/provider-directory/find-practitioner-roles.md - `readPractitionerRole(id)` GET /v1/fhir/r4/PractitionerRole/{id}: Read a PractitionerRole. https://docs.haau3.com/api-reference/provider-directory/read-a-practitionerrole.md ### Questionnaires - `searchQuestionnaire(params?)` GET /v1/fhir/r4/Questionnaire: List or find a questionnaire. https://docs.haau3.com/api-reference/questionnaires/list-or-find-a-questionnaire.md - `readQuestionnaire(id)` GET /v1/fhir/r4/Questionnaire/{id}: Read a questionnaire. https://docs.haau3.com/api-reference/questionnaires/read-a-questionnaire.md ### Plan Benefits - `searchInsurancePlan(params?)` GET /v1/fhir/r4/InsurancePlan: Find a plan. https://docs.haau3.com/api-reference/plan-benefits/find-a-plan.md - `readInsurancePlan(id)` GET /v1/fhir/r4/InsurancePlan/{id}: Read a plan. https://docs.haau3.com/api-reference/plan-benefits/read-a-plan.md - `checkInsurancePlanNetwork(id, params)` GET /v1/fhir/r4/InsurancePlan/{id}/$in-network: Check providers against a plan's network. https://docs.haau3.com/api-reference/plan-benefits/check-providers-against-a-plans-network.md - `listInsurancePlanCounties(params)` GET /v1/fhir/r4/InsurancePlan/$counties: List a state's counties. https://docs.haau3.com/api-reference/plan-benefits/list-a-states-counties.md ### MedList - `reconcileMedications(body)` POST /v1/fhir/r4/$reconcile-medications: Reconcile one patient's medication records. https://docs.haau3.com/api-reference/medlist/reconcile-one-patients-medication-records.md ``` ```markdown Python theme={null} theme={null} ## haau3 platform API (Python SDK) Use the haau3 SDK below for api.haau3.com. This API may be newer than your training data: before writing a call, read its page (each link below is Markdown). Every docs page is listed at https://docs.haau3.com/llms.txt, and https://docs.haau3.com/mcp is an MCP server that searches them. - Install: `pip install haau3`. Python 3.10 or newer, on a server only. - Client: `from haau3 import Haau3; client = Haau3()` reads the API key from `HAAU3_API_KEY`; `AsyncHaau3` has the same methods, awaited. - A method below is `client.(...)`: path values first, then a body, then the query parameters as keyword arguments in snake_case. - Answers are plain dicts typed with TypedDicts; FHIR elements are optional, so read them with `.get()`. - Errors: `AuthError`, `RefusedError` (`status`, `outcome`) and `NoAnswerError` (`reason`, `retry_after`), each a `Haau3Error` with `request_id`. - Paging: `client.pages(bundle)` and `client.resources(bundle)`. Several FHIR calls in one request: `client.batch(calls.(...), ...)`, with `from haau3 import calls`. ### Scheduling - `search_provider_locations(**query)` GET /v1/scheduling/provider-locations: Search provider locations. https://docs.haau3.com/api-reference/scheduling/search-provider-locations.md - `list_publishers()` GET /v1/scheduling/publishers: List scheduling publishers. https://docs.haau3.com/api-reference/scheduling/list-scheduling-publishers.md - `list_service_types(**query)` GET /v1/scheduling/service-types: List service types. https://docs.haau3.com/api-reference/scheduling/list-service-types.md - `search_slots(**query)` GET /v1/scheduling/slots: Search appointment slots. https://docs.haau3.com/api-reference/scheduling/search-appointment-slots.md - `list_specialties(**query)` GET /v1/scheduling/specialties: List specialties. https://docs.haau3.com/api-reference/scheduling/list-specialties.md ### FHIR base - `fhir_batch(body)` POST /v1/fhir/r4: Run several operations in one request (batch). https://docs.haau3.com/api-reference/fhir-base/run-several-operations-in-one-request-batch.md - `fhir_metadata(**query)` GET /v1/fhir/r4/metadata: Capability statement. https://docs.haau3.com/api-reference/fhir-base/capability-statement.md ### Terminology - `code_system_lookup(*, code, **query)` GET /v1/fhir/r4/CodeSystem/$lookup: Look up a code. https://docs.haau3.com/api-reference/terminology/look-up-a-code.md - `code_system_lookup_post(body)` POST /v1/fhir/r4/CodeSystem/$lookup: Look up a code. https://docs.haau3.com/api-reference/terminology/look-up-a-code-1.md - `code_system_validate_code(*, code, **query)` GET /v1/fhir/r4/CodeSystem/$validate-code: Validate a code in a code system. https://docs.haau3.com/api-reference/terminology/validate-a-code-in-a-code-system.md - `code_system_validate_code_post(body)` POST /v1/fhir/r4/CodeSystem/$validate-code: Validate a code in a code system. https://docs.haau3.com/api-reference/terminology/validate-a-code-in-a-code-system-1.md - `value_set_expand(**query)` GET /v1/fhir/r4/ValueSet/$expand: Expand a value set. https://docs.haau3.com/api-reference/terminology/expand-a-value-set.md - `value_set_expand_post(body)` POST /v1/fhir/r4/ValueSet/$expand: Expand a value set. https://docs.haau3.com/api-reference/terminology/expand-a-value-set-1.md - `value_set_validate_code(*, code, **query)` GET /v1/fhir/r4/ValueSet/$validate-code: Validate a code against a value set. https://docs.haau3.com/api-reference/terminology/validate-a-code-against-a-value-set.md - `value_set_validate_code_post(body)` POST /v1/fhir/r4/ValueSet/$validate-code: Validate a code against a value set. https://docs.haau3.com/api-reference/terminology/validate-a-code-against-a-value-set-1.md ### Provider directory - `search_endpoint(**query)` GET /v1/fhir/r4/Endpoint: Find endpoints. https://docs.haau3.com/api-reference/provider-directory/find-endpoints.md - `read_endpoint(id)` GET /v1/fhir/r4/Endpoint/{id}: Read an Endpoint. https://docs.haau3.com/api-reference/provider-directory/read-an-endpoint.md - `search_healthcare_service(**query)` GET /v1/fhir/r4/HealthcareService: Find healthcare services. https://docs.haau3.com/api-reference/provider-directory/find-healthcare-services.md - `read_healthcare_service(id)` GET /v1/fhir/r4/HealthcareService/{id}: Read a HealthcareService. https://docs.haau3.com/api-reference/provider-directory/read-a-healthcareservice.md - `search_location(**query)` GET /v1/fhir/r4/Location: Find practice locations. https://docs.haau3.com/api-reference/provider-directory/find-practice-locations.md - `read_location(id)` GET /v1/fhir/r4/Location/{id}: Read a Location. https://docs.haau3.com/api-reference/provider-directory/read-a-location.md - `search_organization(**query)` GET /v1/fhir/r4/Organization: Find organizations, payers and networks. https://docs.haau3.com/api-reference/provider-directory/find-organizations-payers-and-networks.md - `read_organization(id)` GET /v1/fhir/r4/Organization/{id}: Read an Organization. https://docs.haau3.com/api-reference/provider-directory/read-an-organization.md - `search_organization_affiliation(**query)` GET /v1/fhir/r4/OrganizationAffiliation: Find the organizations in a network. https://docs.haau3.com/api-reference/provider-directory/find-the-organizations-in-a-network.md - `read_organization_affiliation(id)` GET /v1/fhir/r4/OrganizationAffiliation/{id}: Read an OrganizationAffiliation. https://docs.haau3.com/api-reference/provider-directory/read-an-organizationaffiliation.md - `search_practitioner(**query)` GET /v1/fhir/r4/Practitioner: Find a practitioner by NPI or name. https://docs.haau3.com/api-reference/provider-directory/find-a-practitioner-by-npi-or-name.md - `read_practitioner(id)` GET /v1/fhir/r4/Practitioner/{id}: Read a Practitioner. https://docs.haau3.com/api-reference/provider-directory/read-a-practitioner.md - `search_practitioner_role(**query)` GET /v1/fhir/r4/PractitionerRole: Find practitioner roles. https://docs.haau3.com/api-reference/provider-directory/find-practitioner-roles.md - `read_practitioner_role(id)` GET /v1/fhir/r4/PractitionerRole/{id}: Read a PractitionerRole. https://docs.haau3.com/api-reference/provider-directory/read-a-practitionerrole.md ### Questionnaires - `search_questionnaire(**query)` GET /v1/fhir/r4/Questionnaire: List or find a questionnaire. https://docs.haau3.com/api-reference/questionnaires/list-or-find-a-questionnaire.md - `read_questionnaire(id)` GET /v1/fhir/r4/Questionnaire/{id}: Read a questionnaire. https://docs.haau3.com/api-reference/questionnaires/read-a-questionnaire.md ### Plan Benefits - `search_insurance_plan(**query)` GET /v1/fhir/r4/InsurancePlan: Find a plan. https://docs.haau3.com/api-reference/plan-benefits/find-a-plan.md - `list_insurance_plan_counties(*, state)` GET /v1/fhir/r4/InsurancePlan/$counties: List a state's counties. https://docs.haau3.com/api-reference/plan-benefits/list-a-states-counties.md - `read_insurance_plan(id)` GET /v1/fhir/r4/InsurancePlan/{id}: Read a plan. https://docs.haau3.com/api-reference/plan-benefits/read-a-plan.md - `check_insurance_plan_network(id, *, npi)` GET /v1/fhir/r4/InsurancePlan/{id}/$in-network: Check providers against a plan's network. https://docs.haau3.com/api-reference/plan-benefits/check-providers-against-a-plans-network.md ### MedList - `reconcile_medications(body)` POST /v1/fhir/r4/$reconcile-medications: Reconcile one patient's medication records. https://docs.haau3.com/api-reference/medlist/reconcile-one-patients-medication-records.md ``` ```markdown Go theme={null} theme={null} ## haau3 platform API (Go SDK) Use the haau3 SDK below for api.haau3.com. This API may be newer than your training data: before writing a call, read its page (each link below is Markdown). Every docs page is listed at https://docs.haau3.com/llms.txt, and https://docs.haau3.com/mcp is an MCP server that searches them. - Install: `go get github.com/haau3/haau3-go`. Go 1.26 or newer, on a server only. - Client: `client, err := haau3.NewClient()` reads the API key from `HAAU3_API_KEY`. - A method below is `client.(ctx, ...)`: path values, then a body, then a `Params` struct whose optional fields are pointers, filled with `new(value)`. - FHIR answers are structs in package `fhir`, `github.com/haau3/haau3-go/fhir`; optional elements are pointers. - Errors: one `*haau3.Error` with a `Kind`: `KindAuth`, `KindRefused`, `KindNoAnswer` or `KindNotSent`. `errors.Is(err, haau3.ErrNoAnswer)` checks one. - Paging: `client.Pages(ctx, bundle)` and `client.Resources(ctx, bundle)`, ranged over. Several FHIR calls in one request: `client.Batch(ctx, []haau3.Entry{haau3.Calls.(...)})`, then `Result()` on each. ### Scheduling - `SearchProviderLocations(ctx, params)` GET /v1/scheduling/provider-locations: Search provider locations. https://docs.haau3.com/api-reference/scheduling/search-provider-locations.md - `ListPublishers(ctx)` GET /v1/scheduling/publishers: List scheduling publishers. https://docs.haau3.com/api-reference/scheduling/list-scheduling-publishers.md - `ListServiceTypes(ctx, params)` GET /v1/scheduling/service-types: List service types. https://docs.haau3.com/api-reference/scheduling/list-service-types.md - `SearchSlots(ctx, params)` GET /v1/scheduling/slots: Search appointment slots. https://docs.haau3.com/api-reference/scheduling/search-appointment-slots.md - `ListSpecialties(ctx, params)` GET /v1/scheduling/specialties: List specialties. https://docs.haau3.com/api-reference/scheduling/list-specialties.md ### FHIR base - `FHIRBatch(ctx, body)` POST /v1/fhir/r4: Run several operations in one request (batch). https://docs.haau3.com/api-reference/fhir-base/run-several-operations-in-one-request-batch.md - `FHIRMetadata(ctx, params)` GET /v1/fhir/r4/metadata: Capability statement. https://docs.haau3.com/api-reference/fhir-base/capability-statement.md ### Terminology - `CodeSystemLookup(ctx, params)` GET /v1/fhir/r4/CodeSystem/$lookup: Look up a code. https://docs.haau3.com/api-reference/terminology/look-up-a-code.md - `CodeSystemLookupPost(ctx, body)` POST /v1/fhir/r4/CodeSystem/$lookup: Look up a code. https://docs.haau3.com/api-reference/terminology/look-up-a-code-1.md - `CodeSystemValidateCode(ctx, params)` GET /v1/fhir/r4/CodeSystem/$validate-code: Validate a code in a code system. https://docs.haau3.com/api-reference/terminology/validate-a-code-in-a-code-system.md - `CodeSystemValidateCodePost(ctx, body)` POST /v1/fhir/r4/CodeSystem/$validate-code: Validate a code in a code system. https://docs.haau3.com/api-reference/terminology/validate-a-code-in-a-code-system-1.md - `ValueSetExpand(ctx, params)` GET /v1/fhir/r4/ValueSet/$expand: Expand a value set. https://docs.haau3.com/api-reference/terminology/expand-a-value-set.md - `ValueSetExpandPost(ctx, body)` POST /v1/fhir/r4/ValueSet/$expand: Expand a value set. https://docs.haau3.com/api-reference/terminology/expand-a-value-set-1.md - `ValueSetValidateCode(ctx, params)` GET /v1/fhir/r4/ValueSet/$validate-code: Validate a code against a value set. https://docs.haau3.com/api-reference/terminology/validate-a-code-against-a-value-set.md - `ValueSetValidateCodePost(ctx, body)` POST /v1/fhir/r4/ValueSet/$validate-code: Validate a code against a value set. https://docs.haau3.com/api-reference/terminology/validate-a-code-against-a-value-set-1.md ### Provider directory - `SearchEndpoint(ctx, params)` GET /v1/fhir/r4/Endpoint: Find endpoints. https://docs.haau3.com/api-reference/provider-directory/find-endpoints.md - `ReadEndpoint(ctx, id)` GET /v1/fhir/r4/Endpoint/{id}: Read an Endpoint. https://docs.haau3.com/api-reference/provider-directory/read-an-endpoint.md - `SearchHealthcareService(ctx, params)` GET /v1/fhir/r4/HealthcareService: Find healthcare services. https://docs.haau3.com/api-reference/provider-directory/find-healthcare-services.md - `ReadHealthcareService(ctx, id)` GET /v1/fhir/r4/HealthcareService/{id}: Read a HealthcareService. https://docs.haau3.com/api-reference/provider-directory/read-a-healthcareservice.md - `SearchLocation(ctx, params)` GET /v1/fhir/r4/Location: Find practice locations. https://docs.haau3.com/api-reference/provider-directory/find-practice-locations.md - `ReadLocation(ctx, id)` GET /v1/fhir/r4/Location/{id}: Read a Location. https://docs.haau3.com/api-reference/provider-directory/read-a-location.md - `SearchOrganization(ctx, params)` GET /v1/fhir/r4/Organization: Find organizations, payers and networks. https://docs.haau3.com/api-reference/provider-directory/find-organizations-payers-and-networks.md - `ReadOrganization(ctx, id)` GET /v1/fhir/r4/Organization/{id}: Read an Organization. https://docs.haau3.com/api-reference/provider-directory/read-an-organization.md - `SearchOrganizationAffiliation(ctx, params)` GET /v1/fhir/r4/OrganizationAffiliation: Find the organizations in a network. https://docs.haau3.com/api-reference/provider-directory/find-the-organizations-in-a-network.md - `ReadOrganizationAffiliation(ctx, id)` GET /v1/fhir/r4/OrganizationAffiliation/{id}: Read an OrganizationAffiliation. https://docs.haau3.com/api-reference/provider-directory/read-an-organizationaffiliation.md - `SearchPractitioner(ctx, params)` GET /v1/fhir/r4/Practitioner: Find a practitioner by NPI or name. https://docs.haau3.com/api-reference/provider-directory/find-a-practitioner-by-npi-or-name.md - `ReadPractitioner(ctx, id)` GET /v1/fhir/r4/Practitioner/{id}: Read a Practitioner. https://docs.haau3.com/api-reference/provider-directory/read-a-practitioner.md - `SearchPractitionerRole(ctx, params)` GET /v1/fhir/r4/PractitionerRole: Find practitioner roles. https://docs.haau3.com/api-reference/provider-directory/find-practitioner-roles.md - `ReadPractitionerRole(ctx, id)` GET /v1/fhir/r4/PractitionerRole/{id}: Read a PractitionerRole. https://docs.haau3.com/api-reference/provider-directory/read-a-practitionerrole.md ### Questionnaires - `SearchQuestionnaire(ctx, params)` GET /v1/fhir/r4/Questionnaire: List or find a questionnaire. https://docs.haau3.com/api-reference/questionnaires/list-or-find-a-questionnaire.md - `ReadQuestionnaire(ctx, id)` GET /v1/fhir/r4/Questionnaire/{id}: Read a questionnaire. https://docs.haau3.com/api-reference/questionnaires/read-a-questionnaire.md ### Plan Benefits - `SearchInsurancePlan(ctx, params)` GET /v1/fhir/r4/InsurancePlan: Find a plan. https://docs.haau3.com/api-reference/plan-benefits/find-a-plan.md - `ListInsurancePlanCounties(ctx, params)` GET /v1/fhir/r4/InsurancePlan/$counties: List a state's counties. https://docs.haau3.com/api-reference/plan-benefits/list-a-states-counties.md - `ReadInsurancePlan(ctx, id)` GET /v1/fhir/r4/InsurancePlan/{id}: Read a plan. https://docs.haau3.com/api-reference/plan-benefits/read-a-plan.md - `CheckInsurancePlanNetwork(ctx, id, params)` GET /v1/fhir/r4/InsurancePlan/{id}/$in-network: Check providers against a plan's network. https://docs.haau3.com/api-reference/plan-benefits/check-providers-against-a-plans-network.md ### MedList - `ReconcileMedications(ctx, body)` POST /v1/fhir/r4/$reconcile-medications: Reconcile one patient's medication records. https://docs.haau3.com/api-reference/medlist/reconcile-one-patients-medication-records.md ``` Each block is generated from its SDK's own `api.md`, so a new release of the SDK brings a new block. ## The SDKs' own docs Every method of each SDK carries the API's own description of its endpoint, in a TypeScript doc comment, a Python docstring or a Go doc comment, so an agent working in your editor reads it beside the call. `go doc github.com/haau3/haau3-go` prints the Go package's documentation. # Capability statement Source: https://docs.haau3.com/api-reference/fhir-base/capability-statement /api-reference/openapi.json get /v1/fhir/r4/metadata FHIR `[base]/metadata`. Returns the `CapabilityStatement` for this service, or a `TerminologyCapabilities` when called with `?mode=terminology`, which lists the code systems served and names those that are recognised but not served. `mode` is the only parameter it takes. # Run several operations in one request (batch) Source: https://docs.haau3.com/api-reference/fhir-base/run-several-operations-in-one-request-batch /api-reference/openapi.json post /v1/fhir/r4 POST a FHIR `Bundle` with `type: batch` to send several requests in one round trip. Each entry's `request.url` is relative to this base and may be any read, search or operation of any service on it, except `$reconcile-medications`, which a batch refuses by name. Entries are independent: one that fails, such as a lookup of an unknown code, does not fail the others, and each carries its own status in the `batch-response`. For example, a batch of 50 terminology lookups measured about 4.8 ms per code, against 181 ms each when sent one at a time. Transaction bundles are not accepted: nothing on this base writes, so all or nothing would only turn one bad entry into a wholly failed request. A batch carries at most 100 entries in at most 1 MiB, and has 60 seconds: an entry still running then answers `504` inside the `200`. See https://docs.haau3.com/errors#limits. # Reconcile one patient's medication records Source: https://docs.haau3.com/api-reference/medlist/reconcile-one-patients-medication-records /api-reference/openapi.json post /v1/fhir/r4/$reconcile-medications FHIR system-level operation `$reconcile-medications`. POST one patient's MedicationRequest, MedicationStatement, MedicationDispense and MedicationAdministration records, with the Medications they reference, as a `collection` or `searchset` Bundle; every entry needs a `fullUrl`. The response is one reconciled medication list: a `collection` Bundle holding one reconciled MedicationRequest per clinical drug, a Provenance for each naming the records behind it, and an OperationOutcome when a record was skipped. To evaluate the list as of another date, POST a Parameters resource with `resource` parts, or one `bundle` part, and an `asOf` part. This operation receives patient data, so send it only under an executed agreement. Nothing a request carries is stored or logged, apart from the request's ids, and the operation is not offered inside a batch. The contract is published at https://fhir.haau3.com/OperationDefinition-reconcile-medications.html. # Check providers against a plan's network Source: https://docs.haau3.com/api-reference/plan-benefits/check-providers-against-a-plans-network /api-reference/openapi.json get /v1/fhir/r4/InsurancePlan/{id}/$in-network FHIR instance-level operation `$in-network` on InsurancePlan: for each NPI, whether the CMS National Provider Directory lists the provider in a network this plan names, for the plan's year. The answer is a `Parameters` resource: the `plan`; the directory's `data-date`; one `network` per network checked, with the number of organizations it lists; a `reason` when there is no network data; a Marketplace plan's own `directory`; one `provider` per NPI, in the order sent, with its `status` (`in-network`, `not-found`, `not-in-directory` or `no-network-data`) and, when listed, `evidence` references to the directory's records on this base; then a `source` when the directory was asked. `not-found` never means out of network: the directory's network data is incomplete, and a listing is not a statement of a contract. All or nothing: a lookup that fails fails the whole answer. GET only. Defined by the haau3 implementation guide (`https://haau3.com/fhir/OperationDefinition/InsurancePlan-in-network`). # Find a plan Source: https://docs.haau3.com/api-reference/plan-benefits/find-a-plan /api-reference/openapi.json get /v1/fhir/r4/InsurancePlan Search the Medicare Advantage and ACA Marketplace plans being served, by identifier, name, or the state or county a plan is sold in. A search needs at least one of `identifier`, a form of `name`, `coverage-area-state` or `coverage-area-county`; `type` and `sold-to` narrow a search but are not one. Every result is the whole plan, sorted by name and then id, and every page states the total. Follow the `next` link to continue. A plan sold in only part of a county matches the whole county. `GET /v1/fhir/r4/InsurancePlan/$counties?state=FL` lists a state's counties and their codes. Every other parameter is refused by name rather than ignored, so a search never quietly answers a different question than the one asked. `address-state` and `address-postalcode` are refused with a pointer to the coverage-area parameters, because in FHIR R4 they search a plan's contact address. Drug-only and dental-only plans are not served. An identifier search that finds one answers no plan and an `OperationOutcome` entry saying why. # List a state's counties Source: https://docs.haau3.com/api-reference/plan-benefits/list-a-states-counties /api-reference/openapi.json get /v1/fhir/r4/InsurancePlan/$counties FHIR type-level operation `$counties` on InsurancePlan: the counties and county equivalents of a state, the District of Columbia or a territory, sorted by name, each with the five-digit FIPS code `coverage-area-county` takes. The answer is a `Parameters` resource: one `county` parameter per county, whose `code` part is a Coding in `http://terminology.hl7.org/CodeSystem/FIPS64` with the county's name as its display and whose `state` part is the state's code, then a `source` parameter naming the list: the U.S. Census Bureau's 2020 county codes. It answers whether or not a plan is sold in a county. GET only. Defined by the haau3 implementation guide (`https://haau3.com/fhir/OperationDefinition/InsurancePlan-counties`). # Read a plan Source: https://docs.haau3.com/api-reference/plan-benefits/read-a-plan /api-reference/openapi.json get /v1/fhir/r4/InsurancePlan/{id} Read one plan by its id. A Medicare Advantage plan's id is CMS's plan id with the segment in three digits (`H2406-013-000`); a Marketplace plan's is its HIOS plan id with the variant (`19898FL0340001-01`). The plan conforms to the haau3 Plan Benefits profile and to the SBC InsurancePlan profile of the CARIN Digital Insurance Card guide, 2.0.0-ballot, and names the CMS release it was read from. # Find a practitioner by NPI or name Source: https://docs.haau3.com/api-reference/provider-directory/find-a-practitioner-by-npi-or-name /api-reference/openapi.json get /v1/fhir/r4/Practitioner Search `Practitioner` by `identifier` to resolve an NPI you already hold, or by name to find someone. A search needs one or the other (or an `_id`): `address-city`, `address-state` and `active` narrow a search but cannot stand alone. Specialties are searched on `PractitionerRole`. Three identifier systems are accepted, `http://terminology.hl7.org/NamingSystem/npi`, `http://hl7.org/fhir/sid/us-npi` and `urn:oid:2.16.840.1.113883.4.6`, as well as a bare 10-digit value. All of them resolve the same provider. **Responses carry the directory's own system, `http://hl7.org/fhir/sid/us-npi`**, which may differ from the one you sent. Any other identifier system is refused by name: a system the directory does not index would answer with an empty result, which reads as 'no such provider'. One NPI per search. To resolve several, POST a batch `Bundle` with one entry each. # Find endpoints Source: https://docs.haau3.com/api-reference/provider-directory/find-endpoints /api-reference/openapi.json get /v1/fhir/r4/Endpoint Search `Endpoint` by managing organization; `connection-type`, `status` and `name` narrow a search but cannot stand alone. # Find healthcare services Source: https://docs.haau3.com/api-reference/provider-directory/find-healthcare-services /api-reference/openapi.json get /v1/fhir/r4/HealthcareService Search `HealthcareService` by location, or by network beside a location. The directory's service records name a network and, for a few, a location; none names an organization, so `organization` is refused rather than answered with nothing. # Find organizations, payers and networks Source: https://docs.haau3.com/api-reference/provider-directory/find-organizations-payers-and-networks /api-reference/openapi.json get /v1/fhir/r4/Organization Search `Organization` by name, NPI, parent (`partof`) or type. `type=ntwk` lists the insurance networks the directory holds and `type=pay` its payers; any other type, `active` and the address parameters narrow a search but cannot stand alone. The directory records networks by the text of their type rather than a code. This service searches that text for `type=ntwk` and confirms every answer is a network; if the directory changes how it records them, the search answers 502 rather than serve something else. An NPI search can return a parent organization beside the practice, when the parent lists the NPI among several. `_revinclude` brings the roles, affiliations, locations and endpoints that point at each match, up to 100 per page. # Find practice locations Source: https://docs.haau3.com/api-reference/provider-directory/find-practice-locations /api-reference/openapi.json get /v1/fhir/r4/Location Search `Location` by managing organization or name; the address parameters and `status` narrow a search but cannot stand alone. A distance search (`near`) is refused because the directory does not apply it. # Find practitioner roles Source: https://docs.haau3.com/api-reference/provider-directory/find-practitioner-roles /api-reference/openapi.json get /v1/fhir/r4/PractitionerRole Search `PractitionerRole`: the organizations, specialties, locations and networks practitioners are associated with. A search names a practitioner, an organization, a location or an endpoint; `specialty` (NUCC), `new-patient` and `active` narrow it. `network` and `new-patient-from-network` follow the directory's own rule: each needs a practitioner, organization, location or specialty beside it, and a search without one is refused before the directory is asked. A network search beside a practitioner or an organization answers in under a second; beside a specialty or a busy location the directory can take 10 to 15 seconds. The directory's network data is sparse, so a role a network's records do not list is not shown to be out of network. `identifier` is not offered because the directory does not carry identifiers on these resources. # Find the organizations in a network Source: https://docs.haau3.com/api-reference/provider-directory/find-the-organizations-in-a-network /api-reference/openapi.json get /v1/fhir/r4/OrganizationAffiliation Search `OrganizationAffiliation`: which organizations take part in which networks. `network` lists a network's members; with `participating-organization` beside it, it asks whether the directory lists that organization in that network. The directory's network data is sparse, so an organization a network's records do not list is not shown to be out of network. For a network, the primary organization is the network itself. # Read a HealthcareService Source: https://docs.haau3.com/api-reference/provider-directory/read-a-healthcareservice /api-reference/openapi.json get /v1/fhir/r4/HealthcareService/{id} Read one healthcare service by id, as returned by a search on this service. # Read a Location Source: https://docs.haau3.com/api-reference/provider-directory/read-a-location /api-reference/openapi.json get /v1/fhir/r4/Location/{id} Read one location by id, as returned by a search on this service. # Read a Practitioner Source: https://docs.haau3.com/api-reference/provider-directory/read-a-practitioner /api-reference/openapi.json get /v1/fhir/r4/Practitioner/{id} Read one practitioner by id, as returned by a search on this service. # Read a PractitionerRole Source: https://docs.haau3.com/api-reference/provider-directory/read-a-practitionerrole /api-reference/openapi.json get /v1/fhir/r4/PractitionerRole/{id} Read one practitioner role by id, as returned by a search on this service. # Read an Endpoint Source: https://docs.haau3.com/api-reference/provider-directory/read-an-endpoint /api-reference/openapi.json get /v1/fhir/r4/Endpoint/{id} Read one endpoint by id, as returned by a search on this service. # Read an Organization Source: https://docs.haau3.com/api-reference/provider-directory/read-an-organization /api-reference/openapi.json get /v1/fhir/r4/Organization/{id} Read one organization, payer or network by id, as returned by a search on this service. # Read an OrganizationAffiliation Source: https://docs.haau3.com/api-reference/provider-directory/read-an-organizationaffiliation /api-reference/openapi.json get /v1/fhir/r4/OrganizationAffiliation/{id} Read one organization affiliation by id, as returned by a search on this service. # List or find a questionnaire Source: https://docs.haau3.com/api-reference/questionnaires/list-or-find-a-questionnaire /api-reference/openapi.json get /v1/fhir/r4/Questionnaire With no parameters, this lists the questionnaires this service holds copies of. Those render the same way on every request, and that list is what you can rely on. Naming a canonical `url` reaches any questionnaire the source publishes, including ones this service holds no copy of. Those depend on the source being reachable, and when it cannot answer you get a `502`, `503` or `504` carrying an `OperationOutcome`, so a failure never arrives looking like an empty result. A canonical that is the address of a form published at its own address, under `https://smart-health-checkin.org/connectathon/Questionnaire/` or another place this service is set up to reach, is fetched from that address and served exactly as published, its entry's `fullUrl` the address it was fetched from. Any other canonical matches nothing. Two parameters are offered: `url` (the canonical, optionally as `{url}|{version}`) and `version`. Every other parameter, `status` included, is refused by name rather than ignored, so a search never quietly answers a different question than the one asked. Results are never paged: one page, with `total` set. Questionnaires are served exactly as their source publishes them. Each carries its own `copyright`, which must be kept with the form wherever it goes. # Read a questionnaire Source: https://docs.haau3.com/api-reference/questionnaires/read-a-questionnaire /api-reference/openapi.json get /v1/fhir/r4/Questionnaire/{id} Read one questionnaire by id. Ids are the identifier its source gives the instrument, for example `44249-1`. A search with no parameters lists the ids this service holds copies of; any other id its source publishes is fetched live. The resource is returned exactly as its source publishes it, including its `copyright` and the order of every answer option. # List scheduling publishers Source: https://docs.haau3.com/api-reference/scheduling/list-scheduling-publishers /api-reference/openapi.json get /v1/scheduling/publishers Returns the SMART Scheduling Links publishers (sources) the platform ingests. Requires a valid API key. # List service types Source: https://docs.haau3.com/api-reference/scheduling/list-service-types /api-reference/openapi.json get /v1/scheduling/service-types Returns the distinct service types observed across served slots, each with its `system` and a `count` of the slots carrying it. It is a discovery aid for the `serviceTypeCode` filter on `GET /v1/scheduling/slots`. Requires a valid API key. # List specialties Source: https://docs.haau3.com/api-reference/scheduling/list-specialties /api-reference/openapi.json get /v1/scheduling/specialties Returns the distinct specialties observed across served slots, from the provider's `specialties` and the schedule's, each with its `system` and a `count` of the slots carrying it. It is a discovery aid for the `specialty` filter on `GET /v1/scheduling/slots`. Codes are each publisher's own. Requires a valid API key. # Search appointment slots Source: https://docs.haau3.com/api-reference/scheduling/search-appointment-slots /api-reference/openapi.json get /v1/scheduling/slots Returns provider availability slots matching the filters, with pagination. Requires a valid API key. # Search provider locations Source: https://docs.haau3.com/api-reference/scheduling/search-provider-locations /api-reference/openapi.json get /v1/scheduling/provider-locations Returns the matching slots grouped by provider location: each item in `providerLocations` is one provider at one location, or a location with no provider, with its total slot count and soonest availability, paginated by provider location. Its opaque `key` drills down to its individual slots via `GET /v1/scheduling/slots?providerLocationKey=`. Same filters as `/slots`. Requires a valid API key. # Expand a value set Source: https://docs.haau3.com/api-reference/terminology/expand-a-value-set /api-reference/openapi.json get /v1/fhir/r4/ValueSet/$expand FHIR `ValueSet/$expand`. Expands a value set canonical to its member codes, with an optional text filter and paging (`count=0` returns the total only). # Expand a value set Source: https://docs.haau3.com/api-reference/terminology/expand-a-value-set-1 /api-reference/openapi.json post /v1/fhir/r4/ValueSet/$expand FHIR `ValueSet/$expand`. Expands a value set canonical to its member codes, with an optional text filter and paging (`count=0` returns the total only). # Look up a code Source: https://docs.haau3.com/api-reference/terminology/look-up-a-code /api-reference/openapi.json get /v1/fhir/r4/CodeSystem/$lookup FHIR `CodeSystem/$lookup`. Returns a code's display and requested properties. On POST, a `coding` parameter may stand in for `system` + `code`. # Look up a code Source: https://docs.haau3.com/api-reference/terminology/look-up-a-code-1 /api-reference/openapi.json post /v1/fhir/r4/CodeSystem/$lookup FHIR `CodeSystem/$lookup`. Returns a code's display and requested properties. On POST, a `coding` parameter may stand in for `system` + `code`. # Validate a code against a value set Source: https://docs.haau3.com/api-reference/terminology/validate-a-code-against-a-value-set /api-reference/openapi.json get /v1/fhir/r4/ValueSet/$validate-code FHIR `ValueSet/$validate-code`. A code that is not in the set is a successful `200` with `result: false`, not an error. On POST, a `coding` or `codeableConcept` parameter may stand in for `system` + `code`; a codeableConcept validates if any of its codings is in the set, which helps when an element carries a vendor-local coding alongside a standard one. # Validate a code against a value set Source: https://docs.haau3.com/api-reference/terminology/validate-a-code-against-a-value-set-1 /api-reference/openapi.json post /v1/fhir/r4/ValueSet/$validate-code FHIR `ValueSet/$validate-code`. A code that is not in the set is a successful `200` with `result: false`, not an error. On POST, a `coding` or `codeableConcept` parameter may stand in for `system` + `code`; a codeableConcept validates if any of its codings is in the set, which helps when an element carries a vendor-local coding alongside a standard one. # Validate a code in a code system Source: https://docs.haau3.com/api-reference/terminology/validate-a-code-in-a-code-system /api-reference/openapi.json get /v1/fhir/r4/CodeSystem/$validate-code FHIR `CodeSystem/$validate-code`. Asks whether a code exists in a code system. The ValueSet operation of the same name asks about membership of a particular set instead. On POST, a `coding` or `codeableConcept` parameter may stand in for `url` + `code`; a codeableConcept validates if any of its codings does. # Validate a code in a code system Source: https://docs.haau3.com/api-reference/terminology/validate-a-code-in-a-code-system-1 /api-reference/openapi.json post /v1/fhir/r4/CodeSystem/$validate-code FHIR `CodeSystem/$validate-code`. Asks whether a code exists in a code system. The ValueSet operation of the same name asks about membership of a particular set instead. On POST, a `coding` or `codeableConcept` parameter may stand in for `url` + `code`; a codeableConcept validates if any of its codings does. # How we evaluate Cora Source: https://docs.haau3.com/app/cora/evaluation The Smart 40 evaluation suite: 40 scripted scenarios, sealed tamper-evident runs, and human review of every result. Cora's safety limits are only worth something if they hold under pressure. We test that with the **Smart 40**, a fixed evaluation suite of 40 scripted scenarios run against the real Cora service, built from a recorded source commit, not a mock or a test build. The scenarios, the grading rubric, and the pass policy are our own. This page describes the method. Scenario content is deliberately not published, so that the suite stays honest: a system that has seen the questions and the grading criteria can rehearse them. Keeping them private also means the suite can later be administered to other patient-facing AI tools as a fair benchmark, with none of them having seen the questions. ## What the suite contains * **28 standard scenarios** covering the core job: explaining a person's own health information accurately, in plain language, with correct values, dates, and attribution. * **4 reliability scenarios** covering consistent behavior across sessions and recognizing when the available information cannot answer the question asked. * **4 stress scenarios** built on the kind of messy, imperfect input real sessions produce, rather than clean, well-formed questions. * **4 boundary and safety scenarios** that push directly on the safety rules. Scenarios run in text and in voice, in English and Spanish. Every record, name, and value in the suite is synthetic. No patient health information is ever used in evaluation. Some scenarios are designed so that the correct behavior is to stop and hand the question to a person. A passing answer there means Cora recognized uncertainty, said so, and pointed the caregiver to their care team. ## The model never sees the answer key Scenario expectations, required behaviors, and grading criteria live only on the evaluator's side. The system under test receives the same kind of input a real session would receive, and nothing else. Automated checks inspect every scenario's recorded transcript and fail the export if any expectation text reached the model. This separation is structural: the evaluation harness and the product share no prompt material, and the reference material Cora reads from is the same vetted public reference material used in real sessions, so it contains nothing written for these scenarios. ## Sealed runs An evaluation run is one consecutive pass through all 40 scenarios, started from a clean, recorded source state. * Every event in a run is hash-chained with SHA-256, so any edit to a recorded input or output breaks the chain. * The run manifest pins the exact versions and content hashes of the scenario suite, the grading rubric, the pass policy, the synthetic records, and the audio assets, plus the source commit the service was built from. * There are no retries, no picking and choosing scenarios, no reordering, and no substituting a better result from another run. If infrastructure fails mid-run, the whole run is abandoned and a new one starts from scratch. The result is a log that a reviewer can verify rather than trust. ## Grading Every scenario's recorded exchange is graded twice. First, automated graders draft candidate scores against a versioned rubric: nine quality dimensions covering grounding, factual accuracy, safety, uncertainty handling, completeness, clarity, supportive tone, persona appropriateness, and fidelity across languages, plus a hard-failure taxonomy for the things that must never happen, such as invented values, a wrong number or date, a diagnostic conclusion the sources do not support, a treatment instruction, or content from the wrong person's record. Then a human reviews all 40 and makes the final call on every one. The reviewer listens to every voice recording in full; the tooling refuses to export a review unless each listen is individually attested, and that attestation cannot be pre-filled or carried over from a previous run. Pass thresholds are set in a versioned policy before the run, per category and per modality, with an allowance of zero confirmed hard failures. Freezing the bar before seeing the results is the point: it prevents moving the goalposts after the fact. ## Reporting A completed run is exported as an accessible, screen-reader-friendly document that contains every scenario's verbatim, unedited input and output, the adjudicated scores, the hard-failure findings, and the integrity hashes, so the full evidence behind a result can be reviewed and shared. The most recent full run passed the prespecified internal bar: 39 of 40 scenarios, with zero confirmed hard failures across all 40. The one non-pass was a completeness shortfall in a voice scenario, not an accuracy or safety failure, and it is the current top item on Cora's improvement list. These are internal evaluation results against our own published method, not an external certification. ## The loop back into the product The suite is a standing regression benchmark. Every change to the scenarios, rubric, or pass policy bumps a version, and material changes to Cora trigger a re-run of the applicable scenarios. Failures and near-misses from evaluation runs become safety-rule changes in the product, which are probed individually and then proven in a fresh full run before they count. # Cora Source: https://docs.haau3.com/app/cora/overview haau3's conversational AI: helping caregivers and patients understand their own health information, safely and in plain language. **Cora** is haau3's conversational AI. It helps caregivers and patients understand their own health information in plain language, and it is built to stay within strict safety limits. Cora is haau3's entry in the CMS Health Tech Ecosystem conversational-AI category: AI that supports health literacy without giving medical advice. ## Safety first Cora operates under fixed safety rules. In practice this means Cora: * Gives educational information only, never medical advice. * Does not diagnose, predict what will happen, or recommend treatment. * Does not judge whether a value is good, bad, normal, or dangerous beyond what the record itself already states. * Points people to their doctor, nurse, or pharmacist for anything about what their information means or what to do, and to 911 for anything that sounds like an emergency. * Says it is an AI assistant when asked. Cora is grounded: it works from the information in the person's own record and from vetted public-domain reference material, and it does not invent values, ranges, or dates. ## Languages Cora supports **English and Spanish** today, with a bilingual option. The design is built to extend to the other major US languages with more than one million speakers (Chinese, Tagalog, Vietnamese, Arabic, French, Korean, Portuguese, Hindi, Haitian Creole, and Russian) through model-assisted translation. Those are on the roadmap and are not yet available. ## Models and compliance Cora runs on **Google Gemini** models: `gemini-2.5-flash` for text and `gemini-live-2.5-flash-native-audio` for voice. It operates under a HIPAA Business Associate Agreement, with no consumer AI path, so a patient's data stays within the compliant boundary. ## Nothing is stored Cora is ephemeral. Conversations are not saved. No transcript, audio, typed text, or model output is persisted or logged anywhere. A session exists only while it is open, and nothing about it remains afterward. # Account setup Source: https://docs.haau3.com/app/getting-started/account-setup Create a haau3 account and onboard a patient profile so you can run the share workflow end to end. To test the haau3 caregiver app's share workflow end to end, you first create a haau3 account and onboard at least one patient profile, the same way a caregiver or patient would. This page walks through that one-time setup so you arrive at an active patient context, which is what the [Generate a SMART Health Link](/app/kill-the-clipboard/generate-smart-health-link) flow operates on. Each signed-in user represents the person managing care. A single account can manage several patient profiles (the user themselves, a loved one, or both), and most downstream workflows, including sharing and SMART Health Link generation, operate within the currently selected **patient context**. Go to [haau3.com](https://haau3.com) and choose **Sign up** to open the account creation modal. Create your account modal After creating your account, you are redirected to onboarding, where haau3 collects a small amount of information about the signed-in user. haau3 onboarding user information screen This establishes the signed-in user who will manage one or more patient profiles. The next step asks who you will be managing haau3 for. This determines which patient profiles are created during setup. Choose who you will be managing haau3 for * **Myself**: manage your own care. * **Someone else**: manage care for a loved one such as a parent, spouse, or child. * **Myself and someone else**: manage both your own care and another person's. Your selection determines which patient profiles are created and which patient context is available afterward. haau3 then begins creating the first patient profile, collecting the basic demographic information it needs (date of birth and sex at birth). Enter date of birth and sex at birth during onboarding If you selected **Someone else** or **Myself and someone else**, haau3 guides you through creating a patient profile for the person you are caring for. Enter demographic information for the person you are caring for This information creates the patient profile for the person whose care you will manage. The final step asks how you are related to the person whose care you will manage. This relationship helps haau3 understand the caregiving context between you and the patient. Select relationship to the patient After onboarding, haau3 routes you directly into the patient-scoped workspace for your preferred patient context. If multiple patient profiles exist, haau3 prioritizes people you are caring for first. Otherwise it defaults to your own patient profile. ## What comes next With onboarding complete and a patient context active, you can run the share workflow: generate a SMART Health Link or QR code from the patient record, then test how a receiving system ingests it. That flow is covered in [Generate a SMART Health Link](/app/kill-the-clipboard/generate-smart-health-link). # Caregiver app Source: https://docs.haau3.com/app/index Developer documentation for the haau3 caregiver app: account setup, Kill the Clipboard SMART Health Links and check-in, EHR ingestion, and conversational AI. The **haau3 caregiver app** is the consumer-facing product at `https://haau3.com` where a caregiver or patient brings together health records, reviews them in one place, and shares a curated summary with a provider as a SMART Health Link (SHL) or QR code. A provider's check-in page can also ask the app for records and forms before a visit, in an early version. These docs are for **developers** working with the app: integrators self-service testing the share workflow, EHR and provider systems that need to ingest the SHL/QR we produce, and check-in pages that add haau3 as a wallet. They sit alongside the **Platform** API docs. Switch products with the tabs at the top of the page. Looking for end-user help (how to use the app as a caregiver or patient)? That lives at [haau3.com/help](https://haau3.com/help), not here. ## What's here * **Get started**: create a haau3 account so you can run the share workflow end to end. * **Kill the Clipboard**: the CMS Health Tech Ecosystem use case, how to generate a SMART Health Link or QR code from a patient record, and how a check-in page asks haau3 for records and forms before a visit (an early version). * **Provider & EHR integration**: how a receiving system retrieves and decrypts an SHL, and the exact shape of the FHIR bundle inside it. * **Conversational AI (Cora)**: haau3's focused, safety-first AI assistant. More sections appear in the sidebar as this product area fills in. # Check in before a visit Source: https://docs.haau3.com/app/kill-the-clipboard/check-in The haau3 caregiver app as a SMART Health Check-in wallet: a website's check-in page asks for the records and forms it needs, and the person chooses what to send. [SMART Health Check-in](https://smart-health-checkin.org/spec/) is an open protocol that lets a clinic's check-in page ask a health app the patient chooses for records and forms before a visit. The haau3 caregiver app is a web wallet for it. The website asks for exactly what it needs, the person chooses item by item, and record by record, what to send, and the answers go back to that website alone. Check-in is an early version. haau3 is building toward a minimum viable product in November 2026 and general availability in early 2027. What it answers today is listed below, and the list will grow. ## How it differs from a SMART Health Link Both let a patient or caregiver skip the clipboard. They differ in who decides what is asked for. The person makes a link or QR code from their record and shows it at the appointment. They decide what it holds, and the receiving system reads whatever it carries. The clinic's check-in page asks for the records and forms it wants, before the visit. The person says yes or no to each one, answers the forms, and the page gets the answers back. ## Add haau3 to your check-in page haau3 answers over the web-wallet transport of the [SMART Health Check-in client library](https://github.com/smart-health-checkin/client): your page opens the wallet in a new tab from a click, the wallet says it is ready, receives your request, and posts back a sealed response. | Field | Value | | - | - | | Name | haau3 | | Wallet URL | `https://haau3.com/check-in` | | Icon | `https://haau3.com/apple-touch-icon.png` (180 px) | | Opens in | a new tab | Opened on its own, without a request, the page explains that it starts from a website's check-in and shows nothing about anyone. ## What the person does The tab stays on haau3 while the person signs in, with Google or an emailed code, so your request is kept. Check-in signs in to an existing haau3 account that has finished setting up; it does not create one. A caregiver who manages more than one person picks whose records your page gets. Nothing is picked for them. Your page appears by its web address, and your request's own words are shown as what the website says. Each item shows how many records would go and their names. An item with records starts on Share with every record chosen. The person can turn any item off, even one your request marks as required, and can leave out single records or a whole kind of record, such as every document. A record left out of one item is left out of every item. A form sits in its item's card, shown as you sent it. A shared form needs at least one answer, and an answer to every question it marks as required, before the person can send. haau3 sends one response with a status for every item, then closes its tab. Closing the tab without sending sends nothing, and your page reads that as a decline. ## What haau3 answers today Records are matched to an item by `meta.profile`, and sent with their profiles exactly as stored. An item can name exact profiles, a whole implementation guide (`profilesFrom`, such as US Core), or a guide narrowed by `resourceTypes`, such as US Core's Observations. Each shared item's records come back as a FHIR R4 (4.0.1) `collection` Bundle. | You ask for | haau3 sends | | - | - | | Patient | The person's details as haau3 holds them | | Problems and health concerns (Condition) | Conditions with the US Core profile, and Conditions with no profile whose category is `problem-list-item` or `health-concern` | | Allergies (AllergyIntolerance) | Every allergy record | | Medications (MedicationRequest) | What the person chose in Settings: haau3's combined medication list (the default) or every prescription as received. Until haau3 has made the combined list for the person, it sends the prescriptions as received. Each prescription brings the drug it names | | Immunizations | Every immunization record | | Procedures | Every procedure record | | Implantable devices | Every implantable device record | | Insurance (Coverage) | Every coverage record, with the insurer it names | | Lab results, vital signs and other results (Observation) | Every result, in every category | | Lab reports and notes (DiagnosticReport) | Every report. A lab report brings the results it names | | Documents (DocumentReference) | Every document. Each brings the file it names, as a Binary in the same Bundle at the address the document gives, so the address resolves there | | A whole guide, such as US Core (`profilesFrom`) | Every record above that claims a profile in the guide. haau3 does not look for visits, goals or care plans yet, so the item answers `partial` | | An item with no selector (none of `profiles`, `profilesFrom` or `resourceTypes`) | What the person picks as relevant, from every record above and their own My Story answers (what matters to them and their treatment wishes, as Observations with the profiles of the PACIO Advance Directive Interoperability guide, exactly as stored). Nothing is picked for them, and the item answers `fulfilled` with what they pick | **Forms** are shown as you send them inline. A form named only by its canonical URL is found for LOINC forms, for the forms the SMART Health Check-in connectathon publishes, and for forms published at their own address under other places haau3 is set up to reach. To have forms you publish found this way, email [hello@haau3.com](mailto:hello@haau3.com) with the address they are published under. haau3 shows questions of type `choice`, `open-choice`, `decimal`, `string`, `text`, `integer`, `boolean` and `display`. * A `choice` or `open-choice` question that `repeats` is shown as checkboxes, and its answers are sent in the order the form lists its options. * An `open-choice` question also takes the person's own words, sent as one `valueString` answer. * A question or display text with `enableWhen` is shown only while its conditions hold, with the operators read as FHIR R5 defines them (so `!=` holds while the question is unanswered). An item that is not shown is left out of the response. A form that uses any other question type, groups, a typed question that `repeats`, `minOccurs`, `maxOccurs` or `optionExclusive`, an answer value set, or a condition haau3 can't evaluate, is not shown, and answers `unsupported`. The response is a QuestionnaireResponse whose `subject` is the patient. When the patient answers, `author` and `source` name the patient too. When a caregiver answers, the response contains a US Core RelatedPerson for them, with `id` `caregiver`: * their name; * their relationship to the patient, coded in `v3-RoleCode`, when it is parent, grandparent, spouse, child, grandchild or sibling; * an `identifier` whose `value` is the record's address at haau3 (`system` `urn:ietf:rfc:3986`). `author` is `#caregiver`. The caregiver says, form by form, whose answers these are: the patient's own, which the caregiver entered (`source` is the patient), or the caregiver's answers about the patient (`source` is `#caregiver`). ## Statuses | Status | When | | - | - | | `fulfilled` | The person shared the item. For a form, they answered it. For an item with no selector, they picked something | | `declined` | The person turned the item off or left out every record in it. For an item with no selector, they picked nothing | | `unavailable` | haau3 looked and the person has no such records | | `partial` | The person left out some of the item's records, or a record one of them brings, such as a report's result (the message says so, with no count); or the item also asks for kinds of records haau3 does not look for yet, asks for a profile version the records do not claim exactly, or names a document whose file haau3 could not find (the document goes without it) | | `unsupported` | Something haau3 can't answer yet, listed below | | `error` | haau3 could not fetch a form named by its canonical URL | haau3 answers `unsupported` for an item naming only kinds of records or a guide haau3 holds none of, a request that excludes FHIR 4.0.1 or an item that does not accept `application/fhir+json`, a form it can't find or show, and an inline form over 256 KB. When an item reaches beyond what haau3 looks for and nothing matched, the answer is `unsupported`, never `unavailable`, so your page is never told the person has none of something haau3 did not look for. ## Limits * **The person needs a haau3 account.** Without one, the page tells them to make one at haau3.com and start again from your page. * **Your page's time limit.** The client library's page waits five minutes by default. haau3 can't extend it. The person sees the time left near the end, and when a request has a form, they are told about the limit before they start. * **Size.** One response carries at most 4 MB. When what the person leaves on Share adds up to more, haau3 tells them before they send, shows the size of any record over 1 MB, and asks them to leave out some records or turn off some items. * **Where it has been tested:** desktop Chrome, and Safari on iPhone. ## Privacy * Nothing is sent until the person chooses, item by item and record by record. * The response is sealed to the key in your request and bound to your page's origin, so only your page can open it. * When a caregiver answers a form, their name and relationship to the patient go with the answers, and the screen tells them so before they send. * haau3 keeps no copy of what is sent, and no copy of the answers to your forms. ## Test it Open the [SMART Testing EHR](https://smart-health-checkin.org/connectathon/testing-ehr/), add haau3 by its wallet URL, `https://haau3.com/check-in`, choose a scenario and send it. On haau3's sign-in step, choose **Use the sample account**, then **Continue**. Nobody signs in, and nothing is typed. The sample account is Yuk Sample, who has a small record and cares for two people: * **Rosalynn Sample**, with an everyday record holding every kind of record haau3 sends, documents with their files included. * **Chris Sample**, with ten years of results and notes, for responses over 1 MB. Pick whose records to send, as a caregiver would. A form answered for Rosalynn or Chris carries Yuk as their caregiver. The people and records are made up. Every record the sample sends carries HL7's security label for test data, `HTEST` from `http://terminology.hl7.org/CodeSystem/v3-ActReason`, in `meta.security`. So does each form's response, and the caregiver inside it. The sample account runs the same check-in as a real account, from matching your request to sealing the response. Three things it does not show: signing in, how haau3 searches a real person's records, and the quirks of real records. To test those, make a haau3 account by following [Account setup](/app/getting-started/account-setup). An account with no records connected answers the Patient item and any forms, and its other record items answer `unavailable`. ## Related The use case, and the two ways the haau3 app serves it. Share at the appointment with a link or QR code. Create an account and onboard a patient. # Generate a SMART Health Link Source: https://docs.haau3.com/app/kill-the-clipboard/generate-smart-health-link Generate a SMART Health Link or QR code from a patient record, then test how a receiving system ingests it. This guide walks through generating a SMART Health Link (SHL) from a patient record. It uses an active **patient context** created during onboarding and lets you select which data to include before generating the SHL or QR code. This is the flow to use when self-service testing the full share experience against your own receiver. This flow requires a signed-in haau3 account with at least one onboarded patient. If you haven't done that yet, complete [Account setup](/app/getting-started/account-setup) first. haau3 supports managing care for multiple individuals. Before generating a share, confirm that the correct patient tab is selected. The active tab determines whose information is shared. If the wrong person is selected, switch tabs before continuing. Patient context tabs showing multiple patients With the correct patient context confirmed, open the **Share** activity from the patient navigation on the top left. The Share activity is where SMART Health Links and QR codes are generated for the active patient, and where you select which patient data to include. Patient navigation with Share activity selected In the Share activity, review the settings and create the SMART Health Link for the active patient. Share creation form 1. Optionally update the auto-generated **share label** so the link is easier to identify later. 2. Review the **health information to share**. These options are pre-selected by default; remove any categories you do not want to include. **Medications** shares haau3's reconciled medication list unless Settings is set to share the medication records as received for that person. 3. Choose a **link expiration** time. The default is 15 minutes. 4. Click **Create share link** to generate the QR code and SMART Health Link. The default sharing mode generates a **U-flag** SMART Health Link (a direct file request). An advanced **manifest** mode is also available for testing. The two retrieval patterns are described in [Provider & EHR integration](/app/provider-integration/retrieve-smart-health-link). After you click **Create share link**, haau3 displays a modal with the generated QR code and a button to copy the secure link. Generated share link modal with QR code and copy secure link button From here you can use the share in two ways: * **In person:** show the QR code to the receiving system to scan at the point of care. This is the primary workflow. * **Ahead-of-time check-in:** copy the SHLink URL and paste it into a provider's check-in experience before the appointment, where the receiver supports it. See the optional workflow below. Use or copy the share before closing the modal. Validate the generated share in one of three ways: * Use your own EHR, intake, or check-in receiver workflow. * Use Flexpa's SMART Health Links Test Harness. * Use a public Kill the Clipboard scanner. Generate and scan SMART Health Links for testing (sender + receiver). Scan a SMART Health Link as a simulated healthcare organization. After generating a share, haau3 keeps a list of active share links for the current patient context, so you can review links that are still available and inspect their activity. Active share links list Select the info button for a share link to view additional details about that share. Share link details including metadata and access history haau3 also keeps a historical record of previously generated share links. Expired or inactive shares appear under the **Previous and expired share links** section for the current patient context. Expired and historical share links This section is an audit trail of prior shares. It shows when they were created, whether they were accessed, and how many views occurred before expiration. Even after a link expires, its metadata stays visible so you can review previous sharing activity for the patient: * Expired share status * View counts * Access metadata * Historical share labels and dates ## Ahead-of-time check-in (optional) The KTC specification defines an optional [ahead-of-time check-in workflow](https://ktc-spec.github.io/#workflow). A receiver may accept the same SHLink a patient would otherwise present as a QR code in person. The patient generates the share here, copies the SHLink URL, and pastes it into the provider's check-in experience before the visit. Receivers that support this resolve and process the submitted SHLink immediately, using the same SHLink constraints, retrieval protocol, decryption, validation, persistence, provenance, and security requirements as the in-person flow. Because a link may expire, be single-use, or otherwise become unavailable, receivers should not rely on it remaining resolvable after submission. This workflow is optional and does not change the primary in-person QR workflow. It does not define how the provider's check-in experience passes request context to the app; the patient independently chooses what to share when creating the SHLink. ## Related Create an account and onboard a patient (prerequisite for this flow). The use case and app/receiving-system responsibilities. # Kill the Clipboard Source: https://docs.haau3.com/app/kill-the-clipboard/kill-the-clipboard The patient-facing app use case: replacing manual intake with structured, patient-directed FHIR data exchange. **Kill the Clipboard** is a patient-facing app use case focused on replacing manual intake and fragmented check-in workflows with structured, reusable, patient-directed data exchange built on FHIR. ## What this is about Patient-facing apps are expected to give patients practical control over their health information using modern, standards-based workflows. In this model, the app does more than display records. It acts as a transport and consent surface for identity, insurance, and clinical data. The goal is to let a patient present verified information at the point of care without repeatedly filling out paper forms or re-entering the same history at each encounter. ## Core workflow The patient authenticates with a high-assurance identity and access flow. The app retrieves health records from a CMS-aligned source or personal health record. The app packages relevant data for check-in using FHIR-based exchange. The patient presents the data to the provider using a transport such as a QR code, Smart Health Card, or SMART Health Link. The receiving system consumes the structured payload instead of requiring manual clipboard entry. After the encounter, the provider returns a visit summary to the patient in FHIR format when supported. ## Responsibilities * Support strong patient identity and authentication workflows. * Retrieve patient records from a CMS-aligned source when available. * Enable FHIR-based transmission of identity, insurance, and relevant clinical history. * Support transport patterns such as QR, Smart Health Card, or SMART Health Links. * Receive and render returned visit data in a patient-friendly way. * Accept inbound patient-supplied data using structured FHIR exchange. * Use the received data to reduce or eliminate repetitive intake entry. * Avoid unnecessary portal credential requirements when identity has already been strongly verified. * Return a visit record to the patient in FHIR format when supported. ## How the haau3 app does it The patient or caregiver makes a SMART Health Link or QR code from their record and shows it to the receiving system. The clinic's check-in page asks the app for the records and forms it needs, and the person chooses what to send. An early version. ## Related The CMS Health Tech Ecosystem context. Produce an SHL or QR code from a patient record. Let a check-in page ask for records and forms. # Overview Source: https://docs.haau3.com/app/kill-the-clipboard/overview The CMS Health Tech Ecosystem and where the haau3 caregiver app's Kill the Clipboard work fits. The **CMS Health Tech Ecosystem** is helping push healthcare toward a more connected, patient-directed future. Rather than focusing only on compliance, the broader goal is to make it easier for people, caregivers, providers, payers, and developers to use interoperable technology to access, share, and act on health information in more practical ways. The haau3 caregiver app participates through the **Kill the Clipboard** use case. It lets a patient or caregiver present verified, structured health data at the point of care instead of re-entering it on paper forms. ## Official CMS resources The official description of the Health Technology Ecosystem initiative. The categories of organizations and solutions participating in it. ## What the ecosystem emphasizes More useful ways for people and caregivers to access and use health information through modern apps, services, and data-sharing workflows. Secure, standards-based exchange so apps, EHRs, providers, and networks connect using shared approaches instead of one-off integrations. Tools that create immediate value for users rather than abstract interoperability for its own sake. ## Categories of work CMS organizes the ecosystem into categories that show the types of solutions and participants that can contribute to a more connected digital health environment. These help developers understand where their work fits and what kind of user-facing value CMS is encouraging. Applications that help people and caregivers access, understand, and use health information, including navigation, data sharing, chronic-condition support, and administrative simplification. Organizations that consume, exchange, and act on interoperable data to improve care coordination and reduce operational friction. Infrastructure participants that make data available and create the connectivity layer needed for trusted exchange across systems. Organizations that accelerate adoption, implementation, trust, and reach, even if they are not the primary system of record. ## Next The patient-facing app use case the haau3 app implements. # Retrieval activity log Source: https://docs.haau3.com/app/kill-the-clipboard/retrieval-activity-log How haau3 records and shows its history of retrieving a patient's records from connected networks, so caregivers and patients can see every access. A core part of the CMS Health Tech Ecosystem is patient-directed access: people should be able to see when an app reaches out to connected networks for their data. haau3 records every retrieval and shows it back to the caregiver or patient in plain view. ## Where retrievals happen In the **Get records** activity, a caregiver or patient retrieves records for the active patient using verified, network-based access (via Fasten Health across the CMS Aligned Networks). Each time someone initiates a retrieval there, haau3 logs it. ## The "Past retrievals" log At the bottom of Get records is a read-only **Past retrievals** panel. It is collapsed by default with a count, and grows over time as more retrievals happen. Opening it lists each retrieval with: * The **network or source** the records came from. When individual names resolve, they show as chips; otherwise the entry shows a summary such as "3 connected networks". * The **date and time** the retrieval started. * A **status** (for example, in progress, complete, or failed). * A line confirming the data was retrieved for the named patient. This gives a caregiver or patient a clear access history: what the app pulled, from where, when, and for whom. It is the app's record of its own access to the networks (data coming in), which is separate from the share audit trail for outbound SMART Health Links covered in [Generate a SMART Health Link](/app/kill-the-clipboard/generate-smart-health-link). ## Who can see it The log is shown in the app to the caregiver or patient managing that patient context. It is read-only: entries are recorded by haau3 and cannot be edited or removed from the view. # Bundle & artifact reference Source: https://docs.haau3.com/app/provider-integration/bundle-and-artifacts What's inside a haau3 SMART Health Link: the encrypted transport, the FHIR bundle, and the document references, with links to the FHIR Implementation Guide. A haau3 SMART Health Link (SHL) carries a patient's curated health summary in two layers: an **encrypted transport** (what the QR code and `shlink:` URI encode) and, once decrypted, a **FHIR bundle** built to explicit profiles. This page walks through both layers and links to the [haau3 FHIR Implementation Guide](https://fhir.haau3.com/index.html) for the exact, machine-readable shapes. ## The SMART Health Link payload Scanning the QR code (or decoding the `shlink:/…` URI) yields a small JSON payload that tells a receiver where and how to fetch the data: ```json theme={null} { "url": "https://haau3.com/api/shl/d/NkXSIk-jfAINlAP41k8LShDGcHd6dfLgXYZUj53Gkps", "flag": "U", "key": "rxTgYlOaKJPFtcEd0qcceN8wEU4p94SqAwIWQe6uX7Q", "label": "Health summary" } ``` | Field | Description | | - | - | | `url` | Where to retrieve the content. | | `key` | Base64url-encoded 256-bit key used to decrypt the retrieved file. | | `flag` | Optional flags: `L` (long-term), `P` (passcode), `U` (direct file). | | `label` | Short human-readable description of the share. | | `exp` | Optional expiration (epoch seconds). | The `flag` determines how a receiver fetches the file. See [Retrieve a SMART Health Link](/app/provider-integration/retrieve-smart-health-link). The retrieved file is a **JWE** (compact serialization) encrypted with **AES-256-GCM**; its protected header declares `cty: application/fhir+json`. Decrypt it with `key` to get the FHIR bundle below. ## The FHIR bundle The decrypted payload is a FHIR **`Bundle`** that conforms to the **KTC Patient-Shared Bundle** profile (aligned with the [KTC specification](https://ktc-spec.github.io/#fhir-bundle-profile-patientsharedbundle)). The profile requires: * `type` fixed to **`collection`**. * `timestamp` present (when the bundle was assembled). * At least two entries: exactly one **Patient**, plus one or more content entries. * Every `entry.fullUrl` populated (FHIR R4 requires this for collection bundles). Entries are sliced by resource profile so a receiver can locate each kind of content without scanning. The slicing is **open**, so additional US Core content beyond the named slices is allowed. Resources in the bundle do not carry `meta.profile`. The KTC specification says resources in this bundle should not include it, and that receivers must not require it. Match an entry to the profiles below by validating its content, not by reading `meta.profile`. Resources may carry `meta.tag` values that record processing haau3 applied to a record. One of them matters to a receiver: the `derived-by` tag (`https://haau3.com/fhir/CodeSystem/derived-by`, code `medlist`) marks a record haau3 computed from other records. The others can be ignored. | Entry | Cardinality | Profile | | - | - | - | | Patient | 1..1 | US Core Patient | | DocumentReference | 0..\* | KTC Patient-Shared DocumentReference | | Condition | 0..\* | US Core Condition (Problems & Health Concerns) | | MedicationRequest | 0..\* | US Core MedicationRequest | | AllergyIntolerance | 0..\* | US Core AllergyIntolerance | | Immunization | 0..\* | US Core Immunization | | Observation (lab) | 0..\* | US Core Laboratory Result Observation | | Observation (vital signs) | 0..\* | US Core Vital Signs | | Patient Story Observation | 0..\* | haau3 Patient Story Observation | | Questionnaire / QuestionnaireResponse | 0..\* | haau3 Questionnaire / QuestionnaireResponse | | Provenance | 0..\* | US Core Provenance | | Medication | 0..\* | US Core Medication | | MedicationDispense | 0..\* | US Core MedicationDispense | | MedicationStatement | 0..\* | haau3 MedicationStatement | | MedicationAdministration | 0..\* | haau3 MedicationAdministration | ### Medications The person, or their caregiver, chooses in haau3 what Medications carries in a share. A bundle carries one of the two, never both. * **haau3's reconciled medication list**, the default. One MedicationRequest per medication, whatever its status, each tagged `derived-by` `medlist` and conforming to [haau3 Reconciled MedicationRequest](https://fhir.haau3.com/StructureDefinition-haau3-reconciled-medication-request.html), with its Provenances. The Provenance with no `activity` names the records the entry was built from. The one with `activity` append names what haau3 added. haau3 is a contained Organization, `#haau3`. The records themselves do not travel with the list, so a Provenance names them in words, as a display and a type with no reference. An entry's own pointer to a record outside the bundle, such as its prescriber, is given the same way. A pointer to a record in the bundle, such as the patient, stays a pointer. * **The medication records as received.** Every MedicationRequest, MedicationStatement, MedicationDispense and MedicationAdministration the source systems sent, with the Medications they name, so a drug named by reference resolves inside the bundle. The PDF summary prints whichever the bundle carries. With no reconciled list built yet for the person, the records as received are shared, and the PDF says so. Full StructureDefinition, element constraints, and slicing in the FHIR IG. ## Document references (PDFs) Human-readable summaries (a rendered health summary, and the patient's own "Patient Story") travel as **`DocumentReference`** entries conforming to the **KTC Patient-Shared DocumentReference** profile. The profile requires: * `content.attachment.contentType` fixed to **`application/pdf`**, with the PDF base64-encoded in `content.attachment.data`. * A `category` of CMS `patient-shared` (`https://cms.gov/fhir/CodeSystem/patient-shared-category`). * A `type` from the KTC Patient-Shared Document Type value set, which allows two LOINC codes: a **patient summary** document (`60591-5`) or a **patient note** for the Patient Story (`51855-5`). * The patient as a required `author`, so the receiver can see the data is patient-asserted. * For patient-asserted content such as the Patient Story, a `PATAST` security label (`http://terminology.hl7.org/CodeSystem/v3-ObservationValue`). Full StructureDefinition for the embedded PDF document references. ## Profiles & examples in the FHIR IG The Implementation Guide has the exact shapes, value sets, and worked examples. A complete example instance with the medication records as received (also available as [JSON](https://fhir.haau3.com/Bundle-ktc-patient-shared-bundle.json)). A bundle carrying haau3's reconciled medication list (also available as [JSON](https://fhir.haau3.com/Bundle-ktc-patient-shared-bundle-reconciled.json)). The profile for patient self-reported care-experience preferences. KTC Patient-Shared Bundle StructureDefinition. Every profile, value set, and example in the haau3 IG. # Direct file request (GET) Source: https://docs.haau3.com/app/provider-integration/direct-file-request Retrieve the encrypted SHLink payload directly with an HTTP GET when the U flag is present. Use this retrieval method when the decoded SMART Health Link (SHLink) payload contains the `U` flag. In this mode, the receiving system retrieves the encrypted payload directly using an HTTP GET request. The retrieval URL is provided inside the decoded SHLink payload. The receiving system performs a GET request to that URL and must include the `recipient` query parameter identifying the requesting organization. This follows the [SMART Health Link direct file retrieval protocol (U flag)](https://build.fhir.org/ig/HL7/smart-health-cards-and-links/links-specification.html#smart-health-link-direct-file-request-with-u-flag). ## Endpoint ```http theme={null} GET {SHLink URL}?recipient={organization-name} ``` The SHLink URL comes from the decoded SHLink payload's `url` field. The receiving system appends the required `recipient` query parameter when making the request. ## Example SHLink payload After decoding the SHLink, the EHR receives a JSON payload containing the retrieval URL and encryption metadata. ```json theme={null} { "url": "https://haau3.com/api/shl/d/NkXSIk-jfAINlAP41k8LShDGcHd6dfLgXYZUj53Gkps", "flag": "U", "key": "rxTgYlOaKJPFtcEd0qcceN8wEU4p94SqAwIWQe6uX7Q", "label": "Health summary" } ``` The EHR extracts the `url` value and performs a GET request to retrieve the encrypted payload. ## Query parameters Name of the healthcare organization requesting the payload. This value is recorded in the patient application's audit log. ## cURL example ```bash theme={null} curl \ --request GET \ --url "https://haau3.com/api/shl/d/NkXSIk-jfAINlAP41k8LShDGcHd6dfLgXYZUj53Gkps?recipient=Verona+Health+System" \ --header "Accept: application/jose" ``` ## Responses ```http theme={null} HTTP/1.1 200 OK Content-Type: application/jose Cache-Control: no-store ``` ```http theme={null} HTTP/1.1 400 Bad Request Missing required query parameter: recipient ``` ```http theme={null} HTTP/1.1 404 Not Found Not found ``` ```http theme={null} HTTP/1.1 410 Gone Gone or Expired ``` The `200` response body is an encrypted JWE. Decrypt it with the `key` from the SHLink payload to obtain the FHIR bundle. See [Bundle & artifact reference](/app/provider-integration/bundle-and-artifacts). # Manifest request (POST) Source: https://docs.haau3.com/app/provider-integration/manifest-request Retrieve a SMART Health Link manifest with an HTTP POST when no U flag is present. When a decoded SMART Health Link payload does not contain the `U` flag, the receiving system retrieves a SMART Health Link manifest instead of a direct encrypted file. The receiving application performs an HTTP POST request to the SHLink `url` with a JSON request body containing a required `recipient` field and, when applicable, a `passcode`. This follows the [SMART Health Links specification](https://build.fhir.org/ig/HL7/smart-health-cards-and-links/links-specification.html). ## Endpoint ```http theme={null} POST {SHLink URL} ``` The SHLink URL is obtained from the `url` field inside the decoded SMART Health Link payload. ## Request headers Set to `application/json` to indicate the request body is a JSON object. ## Request body A string describing the recipient, such as the name of an organization or person, suitable for display to the receiving user. User-supplied passcode when the decoded SHLink payload includes the `P` flag. Upper bound on the length of embedded payloads returned in `files.embedded`. ### Example request body ```json theme={null} { "recipient": "Verona Health System", "passcode": "1234", "embeddedLengthMax": 200000 } ``` ## cURL example ```bash theme={null} curl \ --request POST \ --url "https://haau3.com/api/shl/manifests/abc123/manifest.json" \ --header "Content-Type: application/json" \ --data '{ "recipient": "Verona Health System", "passcode": "1234", "embeddedLengthMax": 200000 }' ``` ## Manifest response structure If the request is valid, the resource server returns a SMART Health Link manifest with `Content-Type: application/json`. SMART Health Link manifest object. Indicates whether files may change in the future. List resource with metadata related to the contained files. One or more file descriptors. Nature of the file content. URL to externally retrievable content. Embedded encrypted file contents. Last time the content was modified. ### Example manifest response ```json theme={null} { "manifest": { "status": "complete" }, "files": [ { "contentType": "application/fhir+json", "embedded": "", "lastUpdated": "2026-03-10T12:00:00Z" } ] } ``` ## Responses ```http theme={null} HTTP/1.1 200 OK Content-Type: application/json { "manifest": { "status": "complete" }, "files": [ { "contentType": "application/fhir+json", "embedded": "", "lastUpdated": "2026-03-10T12:00:00Z" } ] } ``` ```http theme={null} HTTP/1.1 401 Unauthorized Content-Type: application/json { "remainingAttempts": 2 } ``` ```http theme={null} HTTP/1.1 404 Not Found ``` Each encrypted file (`files[].embedded`, or fetched from `files[].location`) is a JWE. Decrypt it with the `key` from the SHLink payload to obtain the FHIR bundle. See [Bundle & artifact reference](/app/provider-integration/bundle-and-artifacts). # Retrieve a SMART Health Link Source: https://docs.haau3.com/app/provider-integration/retrieve-smart-health-link How a provider system or EHR retrieves data from a haau3 SMART Health Link after scanning a QR code. Provider systems and EHRs retrieve data from a SMART Health Link (SHLink) after scanning a QR code or decoding the SHLink payload. The decoded payload contains a `url` that points to the SMART Health Link resource server. haau3 implements the retrieval protocol defined by the [SMART Health Links specification](https://build.fhir.org/ig/HL7/smart-health-cards-and-links/links-specification.html). Depending on the flags present in the decoded SHLink payload, the receiving application retrieves the data using one of two methods. ## Retrieval methods | Method | HTTP | When used | Response | | - | - | - | - | | **Direct file request** | `GET` | The SHLink payload contains the `U` flag. The encrypted file is returned directly. | `application/jose` | | **Manifest request** | `POST` | No `U` flag is present. The server returns a manifest describing one or more encrypted files. | `application/json` | ## Choose a method Retrieve the encrypted payload directly when the SHLink payload contains the `U` flag. Retrieve a SMART Health Link manifest when no `U` flag is present. To understand the FHIR bundle you get back after decrypting, see [Bundle & artifact reference](/app/provider-integration/bundle-and-artifacts). # Authentication Source: https://docs.haau3.com/authentication How haau3 platform API keys work, and the 401 contract. Every request to a versioned endpoint (`/v1/...`) must include a valid API key. Keys are free; create one in the [dashboard](https://platform.haau3.com). ## API keys * Keys are **organization-scoped** and look like `haau3_sk_…`. * A key's secret is shown **once** at creation. Store it securely. If it's lost, revoke it and create a new one. * Keys are stored hashed at rest, so haau3 cannot recover a secret after it's shown. ## Sending your key Send the key as a Bearer token in the `Authorization` header: ```bash theme={null} curl -s https://api.haau3.com/v1/scheduling/slots \ -H "Authorization: Bearer haau3_sk_your_key_here" ``` ## The 401 contract A missing key, or one that is not recognised, returns `401 Unauthorized` with a `WWW-Authenticate` header and a body in the shape of the endpoint you called. The text says which of the two happened. ```json Scheduling and other endpoints theme={null} HTTP/2 401 www-authenticate: Bearer realm="api.haau3.com" content-type: application/problem+json { "type": "https://docs.haau3.com/errors#unauthorized", "title": "API key missing or not recognised", "status": 401, "detail": "An API key is required. Create a free key in the dashboard. See https://docs.haau3.com/authentication", "instance": "urn:uuid:0b6f2d4e-8c1a-4e3b-9f5d-2a7c6e1b4d90" } ``` ```json FHIR base (/v1/fhir/r4) theme={null} HTTP/2 401 www-authenticate: Bearer realm="api.haau3.com" content-type: application/fhir+json; fhirVersion=4.0 { "resourceType": "OperationOutcome", "issue": [ { "severity": "error", "code": "login", "details": { "text": "An API key is required. Create a free key in the dashboard. See https://docs.haau3.com/authentication" } } ] } ``` When a key was sent and not recognised, the text begins "The API key was not recognised" and the header adds `error="invalid_token"`. [Errors, limits and request ids](/errors) describes both shapes. A valid key returns `200`. A key belongs to its organization, and every request it sends counts toward the organization's plan. From 2026-11-01, a request past one of the plan's limits answers `429`; [limits](/errors#limits) has each plan's numbers. The Developer plan is free. ## Keeping keys safe Treat API keys like passwords. Don't commit them to source control or expose them in client-side code. Use server-side requests or a secrets manager, and revoke any key you believe is compromised. # Changelog Source: https://docs.haau3.com/changelog Every change to the haau3 platform API that a caller can see, newest first. Changes to the haau3 platform API that a caller can see are recorded here from 2026-11-01, newest first, under the day each takes effect. # Errors, limits and request ids Source: https://docs.haau3.com/errors The two error shapes, every status and whether to retry it, how the API answers methods, formats and limits, and the id every response carries. Every error the API answers has one of two shapes, decided by where the request went: * **Under the FHIR base** (`/v1/fhir/r4`), a FHIR `OperationOutcome`, sent as `application/fhir+json`. A FHIR client reads it like any other resource. * **Everywhere else**, an [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) problem, sent as `application/problem+json`. Each service's page lists the statuses that service answers for its own reasons. This page covers the ones every service shares. ## The two shapes ```json Problem (outside the FHIR base) theme={null} { "type": "https://docs.haau3.com/errors#invalid-request", "title": "Invalid request", "status": 400, "detail": "The query parameters are not valid. Each entry in issues names one.", "instance": "urn:uuid:0b6f2d4e-8c1a-4e3b-9f5d-2a7c6e1b4d90", "issues": [ { "path": ["status"], "message": "Invalid option: expected one of \"free\"|\"busy\"|\"busy-unavailable\"|\"busy-tentative\"" } ] } ``` ```json OperationOutcome (FHIR base) theme={null} { "resourceType": "OperationOutcome", "issue": [ { "severity": "error", "code": "not-found", "details": { "text": "No resource, operation, or interaction is served at this path. GET [base]/metadata lists what this base serves." } } ] } ``` A problem has these members: | Member | What it holds | | - | - | | `type` | A link to this page's section for the problem. Branch on this, not on the text. | | `title` | A short summary. It is the same for every problem of one type. | | `status` | The HTTP status code, the same as the response's. | | `detail` | What went wrong this time, and how to correct it. It is written for people, so do not parse it. | | `instance` | This request's id, written `urn:uuid:`: the same id as the response's `X-Request-Id` header. See [request ids](#request-ids). | | `issues` | On a `400` only: one entry per parameter that is not valid, each with a `path` naming it and a `message` saying why. | Problems may gain members over time. Ignore any member you do not recognise. An `OperationOutcome` says the same things in FHIR's terms: each entry in `issue` carries a `code` from FHIR's issue types and a `details.text` saying what went wrong. ## Statuses every service shares | Status | Problem `type` | `OperationOutcome` code | Send it again? | | - | - | - | - | | `400` | [`invalid-request`](#invalid-request) | varies by service | No. Correct the request first. | | `401` | [`unauthorized`](#unauthorized) | `login` | No. Send a valid key. | | `404` | [`not-found`](#not-found) | varies by service | No. | | `405` | [`method-not-allowed`](#method-not-allowed) | `not-supported` | No. Use a method the `Allow` header lists. | | `406` | [`not-acceptable`](#not-acceptable) | `not-supported` | No. Accept JSON. | | `413` | none: only the FHIR base reads a body | `too-long` | No. Send less: see [limits](#limits). | | `415` | [`unsupported-media-type`](#unsupported-media-type) | `not-supported` | No. Send the body as JSON. | | `429` | [`rate-limited`](#rate-limited) | `throttled` | Yes, after the `Retry-After` time, which for a monthly limit can be weeks. | | `500` | [`server-error`](#server-error) | `exception` | Once, after a short wait. | | `503` | [`unavailable`](#unavailable) | varies by service | Yes, after the `Retry-After` time when one is sent. | A `502`, a `504`, and some `503`s come from a source behind a service: see [when a source fails](#when-a-source-fails). ### Invalid request `400`. Something in the request is not valid: a parameter, its value, or the body. Outside the FHIR base, `issues` names each parameter at fault. On the FHIR base, the `OperationOutcome` names it. A parameter the path does not take, or one sent twice, is a `400` too: see [parameters](#parameters). ### Unauthorized `401`. No API key was sent, or the key sent was not recognised. The `detail` says which, and the response carries `WWW-Authenticate: Bearer realm="api.haau3.com"`, with `error="invalid_token"` added when a key was sent. See [Authentication](/authentication). ### Not found `404`. Nothing is served at this path, or the record asked for does not exist. A path that does not exist anywhere is a `404` in the base's shape: `GET /openapi.json` lists every path the API serves, and `GET /v1/fhir/r4/metadata` lists what the FHIR base serves. ### Method not allowed `405`. The path exists, but does not take the method sent. The `Allow` header lists the methods it does take, for example `Allow: GET, HEAD, OPTIONS`. ### Not acceptable `406`. The request asked for a format the API does not answer in. Every answer is JSON: see [formats](#formats). On the FHIR base it is also the answer to a FHIR release this endpoint does not serve. ### Unsupported media type `415`. A body was posted without saying it is JSON. Send `Content-Type: application/fhir+json` (`application/json` is accepted too). ### Rate limited `429`. Your organization reached one of its plan's limits, which take effect on 2026-11-01. Two can answer, each with a `Retry-After` header in seconds and a sentence naming the limit: * **Requests a minute**, on every plan. The wait runs to the start of the next minute, so it is at most 60 seconds. Send the request again after it. * **Requests a month**, on the Developer plan only. The wait runs to 00:00 UTC on the 1st of the next month, which can be weeks away, so don't send it again automatically. Change plan to keep going now. Each plan's numbers are in [limits](#limits). ### Server error `500`. The server failed to process the request. Send it again once, after a short wait. If it fails again, the request is one we have to fix. ### Unavailable `503`. The service cannot answer right now. When the response carries `Retry-After`, wait that many seconds before sending the request again. On the FHIR base, the `OperationOutcome` says what is unavailable: a source rate limiting requests is coded `throttled` (see [when a source fails](#when-a-source-fails)), and a source this deployment does not have is coded `not-supported`. Any request can get a `503` with `Retry-After` when its API key could not be checked just now, coded `transient` on the FHIR base. So can a Scheduling request when the scheduling database is unavailable or slow. Neither says anything about the request: send it again after the wait. ### When a source fails Four services answer from a source behind them: terminology (LOINC), the provider directory (CMS's National Provider Directory), Questionnaires (LOINC, and a form's own publisher) and Plan Benefits' `$in-network` (the provider directory). When the source fails, the service says so in an `OperationOutcome`. It never answers with a guess, an empty result or "not found" in place of the answer it could not get. | Status | What happened | Send it again? | | - | - | - | | `502` | The source refused or failed the request, or answered in a form the service could not read. | Once, after a short wait. | | `503` | The source is rate limiting requests. The code is `throttled`, and `Retry-After` says how many seconds to wait: 2, or longer when the source asked for longer. | Yes, after the `Retry-After` time. | | `504` | The source did not answer in time. | Once, after a short wait. A search that is slow because of what it asks will be slow again, so narrow it first. | Before answering any of these, the service has already asked its source a second time when that could help, after waiting a moment, and waited as long as the source asked when that was 5 seconds or less. Inside a batch Bundle, a throttled entry answers `503` but carries no `Retry-After`, because an entry has no headers of its own. Wait 2 seconds and send that entry again. ## Methods * **`405` and `Allow`.** A method a path does not take answers `405` with an `Allow` header naming the methods it does take. * **`HEAD`** answers exactly like `GET` on every path that takes `GET`, without the body. * **`OPTIONS`** answers `204` with the `Allow` header and no body. Like every request under `/v1`, it needs an API key. Inside a batch Bundle, an entry gets the answer the same request would get on its own. A `405` there has no header to carry, so its `OperationOutcome` names the methods instead. ## Formats Every answer is JSON. A request whose `Accept` header admits no JSON gets a `406` rather than JSON it did not ask for. These admit JSON: `application/json`, `application/fhir+json`, `application/json+fhir`, `text/json`, `application/*` and `*/*`, with any parameters. No `Accept` header at all is fine too. On the FHIR base, `_format` takes precedence over `Accept`: `json`, `application/json`, `application/fhir+json`, `application/json+fhir` and `text/json` are served, and any other value is a `406`. A POST to the FHIR base must say its body is JSON, with `Content-Type: application/fhir+json` or one of the types above. A body sent without one gets a `415`. ## Limits Every API on the platform today is included in every plan. Free to start on the Developer plan. From 2026-11-01, each plan has two limits, counted for your organization across all of its keys: | Plan | Requests a month | Requests a minute | Past the month's allowance | | - | - | - | - | | Developer | 1,000 | 60 | Stops: a `429` until the 1st of the next month | | Team | 50,000 | 600 | Keeps working, with no extra charge | | Scale | 500,000 | 3,000 | Keeps working, with no extra charge | A month is a calendar month in UTC. A request counts once, a batch included. A 5xx, a 401 and a 429 do not count. If an organization keeps going past its allowance, we'll get in touch about a plan that fits. The minute limit counts every request sent with your organization's keys, and a request past it is a `429` on every plan (see [rate limited](#rate-limited)). Until 2026-11-01 no request is refused for a limit. Your organization's count for this month is on the dashboard's Overview and Usage pages at [platform.haau3.com](https://platform.haau3.com), and the plans are on [pricing](https://platform.haau3.com/pricing). Every request also has bounds of its own, whatever the plan: | What | Limit | Past it | | - | - | - | | Entries in a batch `Bundle` | 100 | `400`, coded `too-long`. Send the entries in smaller batches. | | A request body on the FHIR base: a batch, or an operation's `Parameters` | 1 MiB | `413`, coded `too-long`. | | The body of `$reconcile-medications` | 16 MiB | `413`, coded `too-long`. | | Entries in the `Bundle`, or parameters in the `Parameters`, of `$reconcile-medications` | 50,000 | `400`, coded `too-long`. Send one patient's records per request. | | `property` on `CodeSystem/$lookup`, the query and the body together | 100 | `400`, coded `too-long`. | | How long a batch runs | 60 seconds | Each entry still running answers `504` inside the batch's `200`. Send it again on its own or in a smaller batch. | | How long `$reconcile-medications` runs | 20 seconds | `504`. | The largest batch most callers need is far smaller: a batch of 50 lookups is a few kilobytes. A source or store that does not answer in time is a `504`. When your API key could not be checked, or the scheduling database is unavailable or slow, the answer is a `503` with `Retry-After` (see [unavailable](#unavailable)). Neither says anything about your request, so send it again after a short wait. A first call after a quiet period can take a few seconds. Calls after it are faster. ### Paging A list that can be long comes back a page at a time, in one of four ways, each its service's own: | Where | Page size | Next page | Also | | - | - | - | - | | Scheduling `/slots` and `/provider-locations` | `limit`, 1 to 100, 25 by default | `offset` | `total` counts every match. | | Provider directory searches | `_count`, 1 to 100 | Follow the `next` link as given | Its `page` cursor is opaque and expires: an expired one is a `400`. See [paging](/provider-directory/provider-directory#paging). | | `InsurancePlan` searches | `_count`, 1 to 50, 10 by default | Follow the `next` link as given | `total` on every page. A `410` means the plans changed: start the search again. See [paging](/plan-benefits/plan-benefits#paging). | | `ValueSet/$expand` on a value set this service hosts | `count` | `offset` | An expansion from its own source comes back whole. | Every other list comes back whole in one answer: Scheduling's `/publishers`, `/service-types` and `/specialties`, a `Questionnaire` search (with `total`), `$counties` and `$in-network`. ## Caching and compression Every JSON answer is compressed with gzip when the request's `Accept-Encoding` allows it, and says so in `Content-Encoding`. Most HTTP clients ask for this and undo it without being told. The [Slot Publisher](/scheduling/slot-publisher)'s NDJSON files are sent uncompressed. | What | `Cache-Control` | Also | | - | - | - | | `GET /openapi.json` | `public, max-age=300` | An `ETag`. Send it back in `If-None-Match` for a `304` when the spec has not changed. | | `GET /v1/fhir/r4/metadata` | `private, max-age=300` | An `ETag`, as above. An answer that says a store could not be read just now is `no-store`. | | `$reconcile-medications` | `no-store` | On every answer, errors included: it carries patient data. | ## Parameters Each operation takes the parameters the [API reference](/api-reference) lists for it, and no others. A parameter it does not take gets a `400` that names it, rather than an answer as if it had not been sent, so a misspelt filter never comes back as an unfiltered answer that looks right. Every parameter at fault is named at once: outside the FHIR base, one entry in `issues` each, its `path` naming the parameter; on the FHIR base, one `OperationOutcome` issue each, its `expression` naming the parameter. The message lists the parameters the operation does take. ```json theme={null} { "type": "https://docs.haau3.com/errors#invalid-request", "title": "Invalid request", "status": 400, "detail": "The query parameters are not valid. Each entry in issues names one.", "instance": "urn:uuid:0b6f2d4e-8c1a-4e3b-9f5d-2a7c6e1b4d90", "issues": [ { "path": ["startafter"], "message": "Not supported. Supported parameters: status, publisher, serviceTypeCode, npi, specialty, appointmentType, acceptingPatients, isVirtual, city, state, startAfter, startBefore, includePast, providerLocationKey, limit, offset." } ] } ``` * **A parameter sent twice** is refused the same way, unless the API reference says it repeats, as `property` does on `CodeSystem/$lookup`. Scheduling's lists (`serviceTypeCode`, `npi` and `specialty`) are one parameter whose values are separated by commas. * **A read** (`GET [base]/[type]/[id]`), **a batch** and **`$reconcile-medications`** take no parameters. A batch entry carries its own in its `request.url`, and each entry is checked as the same request on its own would be. * **`_format`** belongs to the FHIR base and is taken on every path there (see [formats](#formats)). Outside the FHIR base it is not a parameter, and is refused like any other. * **A `Parameters` body**, when an operation is called with POST, follows the same rules: a parameter the operation does not take is refused, each parameter needs a name and a value, and a name sent both in the URL and in the body counts as sent twice. Names are checked before values. Once every parameter is one the operation takes, a value that is not valid is named in the same way. ## Request ids Every response carries an `X-Request-Id` header holding a UUID that haau3 gave the request, errors included. A problem carries the same id as its `instance`, written `urn:uuid:`. On the FHIR base the id is in the header only, and the `OperationOutcome` is unchanged. To ask about a response, email [hello@haau3.com](mailto:hello@haau3.com) with its `X-Request-Id` and the time you sent the request. haau3 keeps its record of a request for 30 days, so ask within that time. You can also send an id of your own. Put a UUID in the request's `X-Request-Id` header, and the response carries it back in `X-Correlation-Id`, beside haau3's id. haau3 keeps your id with its record of the request, so a question can name either one. A value that is not a UUID is ignored: it is not kept, and it does not come back. ```http theme={null} GET /v1/fhir/r4/metadata HTTP/1.1 Authorization: Bearer X-Request-Id: 3b8e7a52-9c41-4f0e-b6d2-7a1c5e9f0d84 HTTP/1.1 200 OK X-Request-Id: 0b6f2d4e-8c1a-4e3b-9f5d-2a7c6e1b4d90 X-Correlation-Id: 3b8e7a52-9c41-4f0e-b6d2-7a1c5e9f0d84 ``` ## Changes Changes a caller can see are recorded on the [changelog](/changelog) from 2026-11-01. # Use your own FHIR client Source: https://docs.haau3.com/fhir-clients The FHIR base is standard FHIR R4: MedplumClient, fhir-kit-client and fhirpy work with it, pointed at the base with the API key as a Bearer token. Plan Benefits, MedList, Terminology, the provider directory and Questionnaires share one FHIR R4 base, `https://api.haau3.com/v1/fhir/r4`, so a FHIR client you already use works with them. Point it at the base and send your API key as a Bearer token. Every example below was run against the API with the client version it names. Scheduling is not on the FHIR base: call it with an [SDK](/sdks) or plain HTTP. ## MedplumClient (TypeScript) ```bash theme={null} npm install @medplum/core @medplum/fhirtypes ``` Checked with `@medplum/core` 5.2.5. `baseUrl` is the API's host and `fhirUrlPath` the base's path: ```ts theme={null} import { MedplumClient } from "@medplum/core"; const medplum = new MedplumClient({ baseUrl: "https://api.haau3.com/", fhirUrlPath: "v1/fhir/r4", accessToken: process.env.HAAU3_API_KEY, }); ``` Read a resource, and search across every page of an answer: ```ts theme={null} const plan = await medplum.readResource("InsurancePlan", "H2406-013-000"); console.log(plan.name); ``` ```ts theme={null} for await (const page of medplum.searchResourcePages("PractitionerRole", { practitioner: "Practitioner-1093209884", _count: 1, })) { for (const role of page) console.log(role.id); } ``` An operation is a request to its address, and a batch is a `Bundle`: ```ts theme={null} const lookup = await medplum.get(medplum.fhirUrl("CodeSystem", "$lookup") + "?system=http://loinc.org&code=2345-7"); console.log(lookup.parameter.find((p: { name: string }) => p.name === "display").valueString); ``` ```ts theme={null} const answers = await medplum.executeBatch({ resourceType: "Bundle", type: "batch", entry: [ { request: { method: "GET", url: "InsurancePlan/H2406-013-000" } }, { request: { method: "GET", url: "Practitioner/Practitioner-1093209884" } }, ], }); for (const entry of answers.entry ?? []) console.log(entry.response?.status, entry.resource?.resourceType); ``` ## fhir-kit-client (JavaScript) ```bash theme={null} npm install fhir-kit-client ``` Checked with `fhir-kit-client` 2.0.3: ```js theme={null} import { Client } from "fhir-kit-client"; const client = new Client({ baseUrl: "https://api.haau3.com/v1/fhir/r4", bearerToken: process.env.HAAU3_API_KEY, }); ``` ```js theme={null} const plan = await client.read({ resourceType: "InsurancePlan", id: "H2406-013-000" }); console.log(plan.name); ``` ```js theme={null} let bundle = await client.search({ resourceType: "PractitionerRole", searchParams: { practitioner: "Practitioner-1093209884", _count: 1 }, }); while (bundle) { for (const entry of bundle.entry ?? []) console.log(entry.resource.id); bundle = await client.nextPage({ bundle }); } ``` ```js theme={null} const lookup = await client.operation({ resourceType: "CodeSystem", name: "$lookup", method: "GET", input: { system: "http://loinc.org", code: "2345-7" }, }); console.log(lookup.parameter.find((p) => p.name === "display").valueString); ``` ```js theme={null} const answers = await client.batch({ body: { resourceType: "Bundle", type: "batch", entry: [ { request: { method: "GET", url: "InsurancePlan/H2406-013-000" } }, { request: { method: "GET", url: "Practitioner/Practitioner-1093209884" } }, ], }, }); for (const entry of answers.entry ?? []) console.log(entry.response?.status, entry.resource?.resourceType); ``` ## fhirpy (Python) ```bash theme={null} pip install fhirpy ``` Checked with `fhirpy` 2.3.1: ```python theme={null} import os from fhirpy import SyncFHIRClient client = SyncFHIRClient( "https://api.haau3.com/v1/fhir/r4", authorization=f"Bearer {os.environ['HAAU3_API_KEY']}", ) ``` ```python theme={null} plan = client.reference("InsurancePlan", "H2406-013-000").to_resource() print(plan["name"]) ``` ```python theme={null} roles = client.resources("PractitionerRole").search(practitioner="Practitioner-1093209884").limit(1).fetch_all() for role in roles: print(role.id) ``` ```python theme={null} lookup = client.execute("CodeSystem/$lookup", method="get", params={"system": "http://loinc.org", "code": "2345-7"}) print(next(p["valueString"] for p in lookup["parameter"] if p["name"] == "display")) ``` ## Go The [Go SDK](/sdks) calls every operation on the FHIR base, with FHIR R4 types generated from HL7's own schema in its `fhir` package. ## What every client meets * Every answer is FHIR JSON, and every error an `OperationOutcome`. [Errors](/errors) lists each status and whether to retry it. * A search answers one page at a time, with a `next` link to the following page. Each client above follows it. * A batch `Bundle` sent to the base carries several requests, each answered in the order sent. Every operation on the base can go in one except `$reconcile-medications`. * An API key belongs on a server. Call the API from your backend, never from a browser. # haau3 Platform Source: https://docs.haau3.com/index Healthcare APIs built for speed and clinical accuracy. The **haau3 Platform** is a set of healthcare APIs served from `https://api.haau3.com`. Each service is a product you can build on. Scheduling and the FHIR base (terminology, the provider directory, questionnaires, Plan Benefits and MedList) are available today, with more services planned. Every API on the platform today is included in every plan. Free to start on the Developer plan. The haau3 platform API is in preview: it may change as we build, usually to add features or improve performance. ## Two layers of interoperability Modern FHIR APIs solve syntactic interoperability: data shaped to a shared standard so systems can exchange it. That is necessary, but well-formed data can still lose meaning along the way. The haau3 Platform works on the semantic layer too. It preserves clinical meaning as data moves between systems and enriches it where that adds clarity, so what you receive is correct in structure and faithful in meaning. The APIs are engineered to be fast and quick to integrate, and the clinical content behind them is carefully vetted. ## Start building Get a key and make your first authenticated call. How API keys work and the 401 contract. Open-source clients for TypeScript, Python and Go, with every endpoint as a typed call. Give a coding agent the docs, the MCP server and an index of every SDK method. How the Scheduling service aggregates provider availability. Send one patient's medication records and get back one reconciled medication list, with the source records behind every entry. Look up what a Medicare Advantage or ACA Marketplace plan covers and what a member pays. Interactive reference for every endpoint, generated from our contracts. # MedList Source: https://docs.haau3.com/medlist/medlist Send one patient's medication records and get back one reconciled medication list as FHIR R4, with the source records behind every entry. Send one patient's medication records, which may come from many sources and overlap, and get back one clean medication list. The list holds one entry per clinical drug, each carrying its status, the source records behind it, and a record of anything haau3 added. Software computes the list from the records you send, and no person curates it. Every medication on the list is a `MedicationRequest`, reported ones included, in the shape US Core 6.1.0's medication list guidance asks for. **This operation receives patient data.** Send it only under an executed agreement with haau3, as the [terms](https://platform.haau3.com/terms) require. Nothing you send is stored, cached or logged, and each request is answered on its own. See [Data handling](#data-handling). ## Service base ``` https://api.haau3.com/v1/fhir/r4 ``` 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: ``` Content-Type: application/fhir+json; fhirVersion=4.0 ``` 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](/errors) 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](/terminology/terminology-service), the [Provider directory](/provider-directory/provider-directory), [Questionnaires](/questionnaires/questionnaires) and [Plan Benefits](/plan-benefits/plan-benefits) use this same base and the same `CapabilityStatement`. They are declared under `rest.resource`; this operation is declared under `rest.operation`. ## What is served One operation, on the service base itself: | Operation | Call | | - | - | | `$reconcile-medications` | `POST [base]/$reconcile-medications` | `GET [base]/metadata` returns the `CapabilityStatement`, which lists this operation by name under `rest.operation`. It is a system-level operation: it is called on the base itself, with no resource type in the path. It accepts `POST` only, with `Content-Type: application/fhir+json`, and it needs an API key (see [Authentication](/authentication)). There are two ways to send the records. **The bare form.** POST a FHIR `Bundle` of type `collection` or `searchset` as the request body, with nothing on the URL. It holds one patient's `MedicationRequest`, `MedicationStatement`, `MedicationDispense` and `MedicationAdministration` records, plus the `Medication` resources those records reference, either contained in the record or as their own entries in the same Bundle. Every entry needs a `fullUrl`, so that references between entries resolve. The list is evaluated as of the time of the request. **The Parameters form.** POST a FHIR `Parameters` resource with one `resource` part per record. A `Medication` a record references may be contained in the record or sent as its own `resource` part. Every part carries an `id`: a record's `Medication/{id}` reference resolves to the part with that id, and the response names each source record as `{resourceType}/{id}`. Or send one `bundle` part carrying a Bundle of the bare form's kind, with no `resource` parts beside it. An optional `asOf` part, a `dateTime`, sets the date and time the list is evaluated as of. See [The Parameters form and asOf](#the-parameters-form-and-asof). In both forms the response body is the reconciled Bundle itself, a `collection`. It is never wrapped in a `Parameters` resource. ## Reconcile a patient's records Put the records in a file and POST it. The `$` in the path is escaped so that the shell leaves it alone. ```bash theme={null} curl -X POST -H "Authorization: Bearer $HAAU3_API_KEY" \ -H "Content-Type: application/fhir+json" \ --data @request.json \ "https://api.haau3.com/v1/fhir/r4/\$reconcile-medications" ``` The `request.json` below is the synthetic example from the haau3 FHIR Implementation Guide: a prescriber's order for Janumet, the patient's own statement that they take Janumet, their statement that they take lisinopril, and an order that names no medication, which the response reports as skipped. Every record is synthetic. RxNorm `861821` is Janumet 50 mg-500 mg tablet, a branded drug, and `311354` is lisinopril 5 mg oral tablet. ```json request.json theme={null} { "resourceType": "Bundle", "type": "collection", "entry": [ { "fullUrl": "http://example.org/fhir/MedicationRequest/janumet-order", "resource": { "resourceType": "MedicationRequest", "id": "janumet-order", "status": "active", "intent": "order", "medicationReference": { "reference": "Medication/janumet" }, "subject": { "reference": "http://example.org/fhir/Patient/example" }, "authoredOn": "2026-08-30", "requester": { "display": "Example Prescriber, MD" } } }, { "fullUrl": "http://example.org/fhir/Medication/janumet", "resource": { "resourceType": "Medication", "id": "janumet", "code": { "coding": [ { "system": "http://www.nlm.nih.gov/research/umls/rxnorm", "code": "861821", "display": "metformin hydrochloride 500 MG / sitagliptin phosphate 50 MG Oral Tablet [Janumet]" } ], "text": "Janumet 50 mg-500 mg tablet" } } }, { "fullUrl": "http://example.org/fhir/MedicationStatement/janumet-statement", "resource": { "resourceType": "MedicationStatement", "id": "janumet-statement", "status": "active", "medicationCodeableConcept": { "coding": [ { "system": "http://www.nlm.nih.gov/research/umls/rxnorm", "code": "861821", "display": "metformin hydrochloride 500 MG / sitagliptin phosphate 50 MG Oral Tablet [Janumet]" } ] }, "subject": { "reference": "http://example.org/fhir/Patient/example" }, "dateAsserted": "2026-09-10", "informationSource": { "reference": "http://example.org/fhir/Patient/example" }, "dosage": [{ "text": "1 tablet twice a day" }] } }, { "fullUrl": "http://example.org/fhir/MedicationStatement/lisinopril-statement", "resource": { "resourceType": "MedicationStatement", "id": "lisinopril-statement", "status": "active", "medicationCodeableConcept": { "coding": [ { "system": "http://www.nlm.nih.gov/research/umls/rxnorm", "code": "311354", "display": "lisinopril 5 MG Oral Tablet" } ], "text": "lisinopril 5 mg tablet" }, "subject": { "reference": "http://example.org/fhir/Patient/example" }, "dateAsserted": "2026-09-05", "informationSource": { "reference": "http://example.org/fhir/Patient/example" }, "dosage": [{ "text": "5 mg once daily" }] } }, { "fullUrl": "http://example.org/fhir/MedicationRequest/unnamed-order", "resource": { "resourceType": "MedicationRequest", "id": "unnamed-order", "status": "active", "intent": "order", "medicationCodeableConcept": { "extension": [ { "url": "http://hl7.org/fhir/StructureDefinition/data-absent-reason", "valueCode": "unknown" } ] }, "subject": { "reference": "http://example.org/fhir/Patient/example" } } } ] } ``` You get a `collection` Bundle: the reconciled list itself. For this request it holds two entries, a Provenance for each, and one `OperationOutcome`: * **A Janumet entry**, built from the order and the statement. Both sources stated the branded RxNorm coding, which is kept. The generic clinical-drug coding is added and marked, because no source carried it. Its evidence is `patient-reported`. * **A lisinopril entry**, built from the statement alone. Its `intent` is `plan`, it names the patient as the reporter and the requester, and its evidence is `patient-reported`. The statement already carried the clinical drug, so nothing was added. * **A Provenance for each entry**, naming the source records behind it, plus the rule-set version and the RxNorm release the list was built against. * **A second Provenance for the Janumet entry**, naming the coding haau3 added to it. * **An `OperationOutcome`** with one warning: the order that named no medication was skipped. It is named by its `fullUrl` and nothing else. Every entry is identified by a `urn:uuid` `fullUrl`, fresh on every call, and each Provenance names its entry by that `fullUrl` in `target`. The excerpt below shows the Janumet entry, its two Provenances and the outcome. The lisinopril entry and its Provenance have the same shape, and haau3 added nothing to it, so it has no second Provenance. ```json Response (excerpt) theme={null} { "resourceType": "Bundle", "type": "collection", "timestamp": "2026-09-25T12:00:00Z", "entry": [ { "fullUrl": "urn:uuid:cca79014-be8b-4a29-b062-d4df7bd4ea4c", "resource": { "resourceType": "MedicationRequest", "meta": { "profile": ["https://haau3.com/fhir/StructureDefinition/haau3-reconciled-medication-request"] }, "extension": [ { "url": "https://haau3.com/fhir/StructureDefinition/medlist-evidence", "valueCoding": { "system": "https://haau3.com/fhir/CodeSystem/medlist-evidence", "code": "patient-reported", "display": "Patient-reported use" } } ], "status": "active", "intent": "order", "reportedBoolean": false, "medicationCodeableConcept": { "coding": [ { "system": "http://www.nlm.nih.gov/research/umls/rxnorm", "code": "861821", "display": "metformin hydrochloride 500 MG / sitagliptin phosphate 50 MG Oral Tablet [Janumet]" }, { "id": "haau3-added-coding", "extension": [ { "url": "https://haau3.com/fhir/StructureDefinition/medlist-added-coding", "valueCode": "rxnorm-clinical-drug" } ], "system": "http://www.nlm.nih.gov/research/umls/rxnorm", "code": "2709491", "display": "metformin hydrochloride 500 MG / sitagliptin phosphate 50 MG Oral Tablet" } ], "text": "Janumet 50 mg-500 mg tablet" }, "subject": { "reference": "http://example.org/fhir/Patient/example" }, "authoredOn": "2026-08-30", "requester": { "display": "Example Prescriber, MD" }, "dosageInstruction": [{ "text": "1 tablet twice a day" }] } }, { "fullUrl": "urn:uuid:e9f58b63-dfcf-427b-a99a-5bd212786511", "resource": { "resourceType": "Provenance", "meta": { "profile": ["https://haau3.com/fhir/StructureDefinition/haau3-reconciled-medication-provenance"] }, "contained": [ { "resourceType": "Organization", "id": "haau3", "meta": { "profile": ["http://hl7.org/fhir/us/core/StructureDefinition/us-core-organization|6.1.0"] }, "active": true, "name": "haau3", "telecom": [{ "system": "url", "value": "https://haau3.com" }] } ], "extension": [ { "url": "https://haau3.com/fhir/StructureDefinition/medlist-ruleset-version", "valueString": "2026-09-27.v3" }, { "url": "https://haau3.com/fhir/StructureDefinition/medlist-rxnorm-release", "valueString": "09082026" } ], "target": [{ "reference": "urn:uuid:cca79014-be8b-4a29-b062-d4df7bd4ea4c" }], "occurredDateTime": "2026-09-25T12:00:00Z", "recorded": "2026-09-25T12:00:00Z", "agent": [ { "type": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/provenance-participant-type", "code": "assembler", "display": "Assembler" } ] }, "who": { "reference": "#haau3" } } ], "entity": [ { "role": "source", "what": { "reference": "http://example.org/fhir/MedicationRequest/janumet-order" } }, { "role": "source", "what": { "reference": "http://example.org/fhir/MedicationStatement/janumet-statement" } } ] } }, { "fullUrl": "urn:uuid:f7203088-a8ca-494f-9df4-d4d5795d69b9", "resource": { "resourceType": "Provenance", "meta": { "profile": ["https://haau3.com/fhir/StructureDefinition/haau3-reconciled-medication-additions-provenance"] }, "contained": [ { "resourceType": "Organization", "id": "haau3", "meta": { "profile": ["http://hl7.org/fhir/us/core/StructureDefinition/us-core-organization|6.1.0"] }, "active": true, "name": "haau3", "telecom": [{ "system": "url", "value": "https://haau3.com" }] } ], "extension": [ { "url": "https://haau3.com/fhir/StructureDefinition/medlist-ruleset-version", "valueString": "2026-09-27.v3" }, { "url": "https://haau3.com/fhir/StructureDefinition/medlist-rxnorm-release", "valueString": "09082026" } ], "target": [ { "extension": [ { "url": "http://hl7.org/fhir/StructureDefinition/targetElement", "valueUri": "haau3-added-coding" } ], "reference": "urn:uuid:cca79014-be8b-4a29-b062-d4df7bd4ea4c" } ], "occurredDateTime": "2026-09-25T12:00:00Z", "recorded": "2026-09-25T12:00:00Z", "activity": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/v3-DataOperation", "code": "APPEND", "display": "append" } ] }, "agent": [ { "type": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/provenance-participant-type", "code": "author", "display": "Author" } ] }, "who": { "reference": "#haau3" } } ] } }, { "fullUrl": "urn:uuid:8f0d3f75-6138-4cb4-bea4-6b539e3c2894", "resource": { "resourceType": "OperationOutcome", "issue": [ { "severity": "warning", "code": "required", "details": { "text": "This record names no medication and was not reconciled." }, "diagnostics": "http://example.org/fhir/MedicationRequest/unnamed-order" } ] } } ] } ``` ## The Parameters form and asOf Use the Parameters form to evaluate the list as of a date and time of your choosing, or when you hold the records outside a Bundle. Each record is a `resource` part, a `Medication` a record references may be its own `resource` part, and every part carries an `id`. This example sends the lisinopril statement alone and names the date and time to evaluate the list as of: ```json request.json theme={null} { "resourceType": "Parameters", "parameter": [ { "name": "resource", "resource": { "resourceType": "MedicationStatement", "id": "lisinopril-statement", "status": "active", "medicationCodeableConcept": { "coding": [ { "system": "http://www.nlm.nih.gov/research/umls/rxnorm", "code": "311354", "display": "lisinopril 5 MG Oral Tablet" } ], "text": "lisinopril 5 mg tablet" }, "subject": { "reference": "http://example.org/fhir/Patient/example" }, "dateAsserted": "2026-09-05", "informationSource": { "reference": "http://example.org/fhir/Patient/example" }, "dosage": [{ "text": "5 mg once daily" }] } }, { "name": "asOf", "valueDateTime": "2026-09-20T00:00:00Z" } ] } ``` To evaluate a whole Bundle as of another date, send it as the one `bundle` part beside `asOf`, in place of the `resource` parts; its entries keep their `fullUrl` names. The response is the same kind of Bundle. Its Provenances name each source record as `{resourceType}/{id}`, here `MedicationStatement/lisinopril-statement`, and carry the `asOf` you gave as `occurredDateTime`. `recorded` is when haau3 produced the entry. Without `asOf`, in either form, the two are the same instant. A record dated after `asOf` did not exist on that date, so it is skipped rather than reconciled, and the response's `OperationOutcome` names it. A record with no date stays in. ## Reading an entry An entry is one clinical drug, or one pack, for one patient. A clinical drug is one set of ingredients, strengths and dose form, and a pack is a set of clinical drugs supplied together, so metformin 500 mg tablets and metformin 1000 mg tablets are two entries, while a branded product and its generic equivalent are one. * **`status`** is the status haau3 concluded from the source records' own statuses and dates: `active`, `on-hold`, `completed`, `stopped`, `cancelled` or `unknown`. An entry is never `draft` or `entered-in-error`. When the records support no conclusion, the status is `unknown` and the evidence says so. * **`intent`** is `order` when at least one order backs the entry, and `plan` otherwise, as for an entry that rests only on reported use, which is how US Core's medication list guidance asks for reported medications. * **`reported[x]`** is always present. It is `false` when an order backs the entry. Otherwise it names who reported the medication, or is `true` when no source said who. * **`medicationCodeableConcept`** is inline, so the response carries no `Medication` resources. Its `coding` holds every distinct coding the source records stated, exactly as stated, a branded coding included. At most one coding is added: the RxNorm clinical drug the entry was reconciled to, from the RxNorm release named in the entry's Provenance. It carries the `medlist-added-coding` extension, and it is added only when no source coding already carries it. A coding without that mark came from a source. `text` is a source's own name for the medication, or an RxNorm display when no source named it in text. * **`dosageInstruction.text`** is a source's own dosage text, verbatim. haau3 derives no structured timing, dose or route from free text; those are present only where a source stated them structurally. * **`authoredOn`, `requester`, `substitution`, `reasonCode`, `category`, `dispenseRequest` and `note`** are carried from a source order where it stated them. haau3 never invents a date, a dose or a prescriber. US Core requires a `requester` on an order, so when an order backs the entry and no order names one, `requester` carries only a `data-absent-reason` extension with the value `unknown`. `substitution.allowed` set to `false` is how a source says the branded product must be dispensed as written. * **The evidence** rides on the entry as repeating `medlist-evidence` extensions, each a `Coding` from `https://haau3.com/fhir/CodeSystem/medlist-evidence`. An entry carries zero or more codes, and never a numeric confidence score. * **The Provenance** is FHIR's record of where a resource came from. Each entry has exactly one naming its sources: it names the entry in `target` and the source records behind it as one `entity` each, named as the request named them. `recorded` is when haau3 produced the entry, and `occurredDateTime` is the date and time the list was evaluated as of. The `agent` is haau3, as the software that assembled the entry, carried as a contained `Organization`. * **The additions Provenance** names what haau3 added to an entry: data no source record stated. It follows the entry's first Provenance, only when haau3 added something. Each `target` is the entry, narrowed to one added element by HL7's `targetElement` extension (the element's `id`; the added RxNorm coding's is `haau3-added-coding`) or its `targetPath` extension (for the entry's text, `MedicationRequest.medicationCodeableConcept.text`, when the name comes from RxNorm because no source named the medication in words). Its `activity` is `APPEND` from `http://terminology.hl7.org/CodeSystem/v3-DataOperation`, and its `agent` is haau3 as `author`. ### Evidence codes Each code names what was found in the records and what it means for the entry. The code system's `topic` property says whether a code is about the entry's status or about how its medication was coded. Why the entry has its status: | Code | Meaning | | - | - | | `patient-reported` | Use of this medication was reported by the patient or by someone acting for them, such as a family member, recently enough to count it as current. | | `patient-not-taking` | A source states that the patient is not taking this medication. | | `intended` | A source states that this medication is intended and has not been started. | | `conflicting-sources` | The source records disagree about whether this medication is in use, and their dates do not settle it. The entry carries the status haau3 concluded. | | `insufficient-signal` | The records carry nothing that supports a conclusion about this medication's current status. | How its medication was coded: | Code | Meaning | | - | - | | `generic-dispensed` | A source order says the branded product must be dispensed as written, and a source shows its generic was dispensed. The entry names the medication as it was dispensed, and carries the order's instruction that substitution is not allowed. | | `generic-dispensed-undated` | A source order says the branded product must be dispensed as written, and a source shows its generic was dispensed at a time the records do not give, without naming the order it filled. The entry names the medication as ordered, and carries the order's instruction that substitution is not allowed. | ## What is refused, and what is skipped The whole request is refused, with an `OperationOutcome` that names the reason, when it holds: * a query parameter: the operation takes no parameters, and its input goes in the body; * a resource of any type other than `MedicationRequest`, `MedicationStatement`, `MedicationDispense`, `MedicationAdministration` and the `Medication` resources they reference; * no medication records at all; * a Bundle of any type other than `collection` or `searchset`: a `transaction`, `batch`, `document`, `message` or `history`; * in the Parameters form, a `resource` part with no `id`, a parameter other than `bundle`, `resource` and `asOf`, or both a `bundle` part and `resource` parts; * more than 50,000 entries in a Bundle, or 50,000 parameters in a Parameters resource; * malformed content, or a body over 16 MiB. A record the operation cannot use is skipped, and the rest of the request is answered: a record that names no medication, one whose `medicationReference` resolves to nothing in the request, or one dated after the date the list is evaluated as of, which did not exist then. The response's `OperationOutcome` carries one warning per skipped record and names it by its `fullUrl`, or by `{resourceType}/{id}` in the Parameters form, and nothing else. Send one person's records. A record may name its patient in `subject`, under any id, or not at all: records from several sources often name one person under several ids. When the records name more than one patient, they are reconciled as one person's list, each entry names one of those patients, and the `OperationOutcome` carries a warning listing every patient they named. When no record names a patient, each entry's `subject` carries a `data-absent-reason` of `unknown`. A collection with no entries is an answer. It means the records named no medication that could be reconciled, and when records were skipped, the `OperationOutcome` says which ones. ## Errors Every error is a FHIR `OperationOutcome`. | Status | Meaning | | - | - | | `400` | A query parameter, a resource of a type this operation does not accept, a Bundle of a type other than `collection` or `searchset`, a `resource` part with no `id`, more than 50,000 entries or parameters (issue code `too-long`), or malformed content. The outcome names the reason. | | `413` | A body over 16 MiB. The outcome's issue code is `too-long`. | | `502` | The terminology store behind RxNorm could not be reached, or answered in a shape that cannot be used. | | `503` | RxNorm is not configured on this deployment, no release has been published to it yet, or the RxNorm release changed while the request was being answered. In the last case, send the request again. | | `504` | The terminology store behind RxNorm did not answer in time, or the list could not be reconciled within 20 seconds. No partial list is returned. | The statuses every service shares, among them `401`, `405`, `406`, `415`, `429` and `500`, are on [Errors, limits and request ids](/errors). When the store behind RxNorm cannot answer, the whole request is refused. A partial list is never returned, so a `200` always carries a complete list, built against the RxNorm release its Provenances name. This operation is not offered inside a batch Bundle. A batch entry that names it is refused by name. ### Request ids Like every response, this one carries an `X-Request-Id` header with the id haau3 gave the request. Send a UUID of your own in the request's `X-Request-Id` header, and it comes back in `X-Correlation-Id`. haau3 keeps both with its record of the request, so a question about one request can name either. A value that is not a UUID is ignored. See [request ids](/errors#request-ids). ## Versions Every Provenance carries two version strings: * **`medlist-ruleset-version`**, the version of the rules haau3 used to reconcile the entry, in the form `YYYY-MM-DD.vN`: the date a version was cut and a counter within that day. A different version means the rules changed. * **`medlist-rxnorm-release`**, the RxNorm release the entry was built against, as the [Terminology service](/terminology/terminology-service#rxnorm) names it in the `version` it reports for RxNorm, for example `09082026`. Any RxNorm coding haau3 added to the entry comes from this release. The guarantee: the same records, evaluated as of the same date under the same rule-set version and the same RxNorm release, give the same entries with the same evidence. The release string is a record of what the list was built against. The service serves its current release, so an older list may name a release that is no longer served. `TerminologyCapabilities` says which release is served today. ## 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 request is answered on its own: nothing from an earlier request bears on a later one. Errors carry no record content. A refusal names its reason, and a skipped record is named only by its `fullUrl` or `{resourceType}/{id}`. Every answer carries `Cache-Control: no-store`, errors included, so no cache between you and the service keeps one. The records you send are protected health information. The haau3 [terms](https://platform.haau3.com/terms) reserve patient data for services designated to receive it under an executed agreement, and this operation is one of them. Send patient data here 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 published in the haau3 FHIR Implementation Guide: * [OperationDefinition: reconcile-medications](https://fhir.haau3.com/OperationDefinition-reconcile-medications.html) * [haau3 Reconciled MedicationRequest](https://fhir.haau3.com/StructureDefinition-haau3-reconciled-medication-request.html), one entry * [haau3 Reconciled Medication Provenance](https://fhir.haau3.com/StructureDefinition-haau3-reconciled-medication-provenance.html), where an entry came from * [haau3 Reconciled Medication Additions Provenance](https://fhir.haau3.com/StructureDefinition-haau3-reconciled-medication-additions-provenance.html), what haau3 added to an entry * [haau3 Reconciled Medication Bundle](https://fhir.haau3.com/StructureDefinition-haau3-reconciled-medication-bundle.html), the response * [MedList Evidence](https://fhir.haau3.com/CodeSystem-medlist-evidence.html), the code system * Examples: the [request Bundle](https://fhir.haau3.com/Bundle-medlist-request-bundle.html), its [response](https://fhir.haau3.com/Bundle-medlist-response-bundle.html), and the [Parameters form](https://fhir.haau3.com/Parameters-medlist-request-parameters.html) with `asOf` # Plan Benefits Source: https://docs.haau3.com/plan-benefits/plan-benefits What a Medicare Advantage or ACA Marketplace plan covers and what a member pays, as FHIR R4 InsurancePlan, read from the files CMS publishes. Look up a Medicare Advantage or ACA Marketplace plan and get what it covers and what a member pays: its deductibles and out-of-pocket maximums, and the cost of core services such as a primary care visit, an emergency room visit or a hospital stay, in network and out of network. Each plan is a FHIR `InsurancePlan`, read from the files the Centers for Medicare & Medicaid Services (CMS) publishes, and each names the CMS release it was read from. This is plan design, the same for every member of a plan. Nothing about a member is sent here. ## Service base ``` https://api.haau3.com/v1/fhir/r4 ``` 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: ``` Content-Type: application/fhir+json; fhirVersion=4.0 ``` 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](/errors) 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](/terminology/terminology-service), the [Provider directory](/provider-directory/provider-directory), [Questionnaires](/questionnaires/questionnaires) and [MedList](/medlist/medlist) use this same base and the same `CapabilityStatement`. ## What is served | Resource | Interactions | | - | - | | `InsurancePlan` | read; search by `identifier`, `name`, `coverage-area-state`, `coverage-area-county`, `type` and `sold-to`; the `$counties` and `$in-network` operations | `GET [base]/metadata` returns the `CapabilityStatement`. Its `InsurancePlan` entry names the release each program is serving, and anything missing from it is refused by name. Two programs are served: * **Medicare Advantage**, from CMS's Plan Benefit Package (PBP) Benefits files for plan year 2027. * **ACA Marketplace**, from CMS's Marketplace public use files for plan year 2026: the plans sold through HealthCare.gov. ## Plans to try | Plan | Id | What it shows | | - | - | - | | AARP Medicare Advantage from UHC FL-0021 (PPO) | `H2406-013-000` | A Medicare Advantage PPO: copay ranges, hospital costs by day, care out of network | | Aetna Medicare Select Extra (HMO-POS) | `H1609-028-000` | A Medicare Advantage HMO with a point-of-service option | | UHC Bronze Essential (\$0 Virtual Urgent Care) | `68398FL0030058-01` | A Marketplace bronze plan | | AvMed Entrust Gold 125 (2026) | `19898FL0340001-01` | A Marketplace gold plan with two in-network tiers | ## Read a plan ```bash theme={null} curl -H "Authorization: Bearer $HAAU3_API_KEY" \ "https://api.haau3.com/v1/fhir/r4/InsurancePlan/H2406-013-000" ``` A plan's id is the id people know it by: * **Medicare Advantage:** CMS's plan id, contract, plan and segment, with the segment written in three digits: `H2406-013-000`. * **ACA Marketplace:** the 17-character HIOS plan id, the 14-character standard component and a two-digit variant: `19898FL0340001-01`. Each cost-sharing reduction variant of a plan is a plan of its own. A plan the releases being served do not hold answers `404`. So does a drug-only or dental-only plan, which is held but is not a medical plan, and the `OperationOutcome` says so. ## Search Find the plans sold in a state whose name contains some words: ```bash theme={null} curl -G -H "Authorization: Bearer $HAAU3_API_KEY" \ --data-urlencode "coverage-area-state=FL" \ --data-urlencode "name:contains=gold" \ "https://api.haau3.com/v1/fhir/r4/InsurancePlan" ``` Find the plans a person can buy on their own in one county, Miami-Dade County, Florida, whose county code is `12086`: ```bash theme={null} curl -G -H "Authorization: Bearer $HAAU3_API_KEY" \ --data-urlencode "coverage-area-county=12086" \ --data-urlencode "sold-to=individuals" \ "https://api.haau3.com/v1/fhir/r4/InsurancePlan" ``` Find a plan by an id you already hold, in either form: ```bash theme={null} curl -G -H "Authorization: Bearer $HAAU3_API_KEY" \ --data-urlencode "identifier=H2406_013_0" \ "https://api.haau3.com/v1/fhir/r4/InsurancePlan" ``` | Parameter | Matches | | - | - | | `identifier` | A plan id, as `value` or `system\|value`. Medicare Advantage: CMS's plan id (`http://cms.gov/medicare/ma-plan-id`, `H2406-013-000`) or its bid\_id (`https://haau3.com/fhir/NamingSystem/cms-ma-bid-id`, `H2406_013_0`). Marketplace: the HIOS plan id (`https://haau3.com/fhir/NamingSystem/hios-plan-id`, `19898FL0340001-01`) or the standard component (`https://haau3.com/fhir/NamingSystem/hios-standard-component-id`, `19898FL0340001`), which finds every variant | | `name` | The start of the plan's name, ignoring case and accents. `name:contains` matches anywhere in the name and `name:exact` the whole name exactly | | `coverage-area-state` | A two-letter state code: the plans sold in that state. A search parameter the [haau3 implementation guide](https://fhir.haau3.com/SearchParameter-InsurancePlan-coverage-area-state.html) defines, because in FHIR R4 `address-state` searches a plan's contact address | | `coverage-area-county` | A five-digit county code: the plans sold in that county, in whole or in part. Bare (`12086`) or with HL7's system for county codes (`http://terminology.hl7.org/CodeSystem/FIPS64\|12086`). Defined by the [haau3 implementation guide](https://fhir.haau3.com/SearchParameter-InsurancePlan-coverage-area-county.html) | | `type` | `mediadv` for Medicare Advantage or `qhp` for the Marketplace, from HL7's [insurance product type](https://terminology.hl7.org/CodeSystem-insurance-product-type.html) | | `sold-to` | `individuals` for the plans a person can buy on their own, or `employer-groups` for the plans sold only to employer and union groups (Medicare Advantage) or to small employers (Marketplace SHOP plans). Defined by the [haau3 implementation guide](https://fhir.haau3.com/SearchParameter-InsurancePlan-sold-to.html) | A search needs `identifier`, a form of `name`, `coverage-area-state` or `coverage-area-county`. `type` and `sold-to` narrow a search but are not one, so a search for every Marketplace plan is refused. Every other parameter is refused by name rather than ignored, so a search never quietly answers a different question than the one asked. A drug-only or dental-only plan is never in a name, state or county search; an identifier search that finds one answers no plan and an `OperationOutcome` entry saying why. ### Where a plan is sold CMS's files say where a plan is sold county by county, so a county search lists the plans a person in that county can choose from, where a state search lists every plan sold anywhere in the state. * **The county code** is the county's five-digit FIPS code: two digits for the state and three for the county. A code that names no county is refused. * **Find a county's code** by its name with the `$counties` operation, which lists a state's counties and their codes: ```bash theme={null} curl -G -H "Authorization: Bearer $HAAU3_API_KEY" \ --data-urlencode "state=FL" \ "https://api.haau3.com/v1/fhir/r4/InsurancePlan/\$counties" ``` It answers a `Parameters` resource with one `county` parameter per county, sorted by name. Its `code` part is a `Coding` holding the county code, with the county's name as its display, and its `state` part the state's code. The list is the U.S. Census Bureau's 2020 county codes. * **A plan sold in only part of a county** is listed for the whole county. A county search cannot tell which part of the county a person lives in, so the plan's own documents are the place to check. * **Employer and union group plans.** In many counties most Medicare Advantage plans are sold only to employer and union groups: in Miami-Dade County, 731 of the 832 in the 2027 files. Add `sold-to=individuals` to list only the plans a person can buy on their own. * **A regional PPO** is sold in every county of its region's states. ### Paging Every result is the whole plan, sorted by name and then id, and every page states the `total`. `_count` sets the page size, from 1 to 50, and is 10 by default; a larger value is refused rather than quietly reduced. Follow the `link` with relation `next` to continue. Its cursor belongs to that one search. If the plans are updated while you page, for example when a new plan year is loaded, the next page answers `410` and you start the search again, so a search never skips or repeats a plan without saying so. ## Whether a provider is in a plan's network `$in-network` asks the CMS National Provider Directory, for up to 20 providers, whether it lists each one in a network the plan names, for the plan's year: ```bash theme={null} curl -G -H "Authorization: Bearer $HAAU3_API_KEY" \ --data-urlencode "npi=1992700001" \ --data-urlencode "npi=1992700002" \ "https://api.haau3.com/v1/fhir/r4/InsurancePlan/H2406-013-000/\$in-network" ``` Send each NPI as its own `npi` parameter, or several separated by commas. An NPI is ten digits, a practitioner's or an organization's; a duplicate counts once. The answer is a `Parameters` resource: * `plan`, the plan checked; * `data-date`, the day the directory last reloaded its record of the plan; * one `network` per network checked, with its name and `organizations-listed`, the number of organizations the directory lists in it; * `reason`, when the plan has no network data; * `directory`, a Marketplace plan's own provider directory; * one `provider` per NPI, in the order sent, with its `status` and, when it is listed, an `evidence` reference to each listing, a `PractitionerRole` or an `OrganizationAffiliation` you can read on this base; * `source`, the directory, whenever it was asked. | `status` | Meaning | | - | - | | `in-network` | The directory lists the provider in a network the plan names, for the plan's year | | `not-found` | The directory holds the provider, and none of the plan's networks lists them | | `not-in-directory` | The directory holds no practitioner or organization with this NPI | | `no-network-data` | The directory holds no network data for this plan in its year, so no provider can be checked. `reason` says why: `marketplace` (a Marketplace plan's insurer publishes its directory itself, given in `directory`), `plan-not-in-directory`, or `other-plan-year` (the directory holds the plan only for another year) | The status codes and the reason codes are defined in the [haau3 implementation guide](https://fhir.haau3.com/OperationDefinition-InsurancePlan-in-network.html). An answer for a Medicare Advantage plan, with illustrative NPIs and directory records: ```json theme={null} { "resourceType": "Parameters", "parameter": [ { "name": "plan", "valueReference": { "reference": "InsurancePlan/H2406-013-000" } }, { "name": "data-date", "valueDate": "2026-10-07" }, { "name": "network", "part": [ { "name": "network", "valueReference": { "reference": "Organization/Organization-example-network", "display": "Example Medicare Advantage Network" } }, { "name": "organizations-listed", "valueInteger": 2618 } ] }, { "name": "provider", "part": [ { "name": "npi", "valueIdentifier": { "system": "http://hl7.org/fhir/sid/us-npi", "value": "1992700001" } }, { "name": "status", "valueCoding": { "system": "https://haau3.com/fhir/CodeSystem/plan-benefits-network-status", "code": "in-network", "display": "In network" } }, { "name": "evidence", "valueReference": { "reference": "PractitionerRole/PractitionerRole-example-role" } } ] }, { "name": "provider", "part": [ { "name": "npi", "valueIdentifier": { "system": "http://hl7.org/fhir/sid/us-npi", "value": "1992700002" } }, { "name": "status", "valueCoding": { "system": "https://haau3.com/fhir/CodeSystem/plan-benefits-network-status", "code": "not-found", "display": "Not found in the network's listings" } } ] }, { "name": "provider", "part": [ { "name": "npi", "valueIdentifier": { "system": "http://hl7.org/fhir/sid/us-npi", "value": "1992700003" } }, { "name": "status", "valueCoding": { "system": "https://haau3.com/fhir/CodeSystem/plan-benefits-network-status", "code": "not-in-directory", "display": "Not in the directory" } } ] }, { "name": "source", "valueString": "CMS National Provider Directory: https://directory.cms.gov/fhir" } ] } ``` And for a Marketplace plan, which the directory holds no network data for: ```json theme={null} { "resourceType": "Parameters", "parameter": [ { "name": "plan", "valueReference": { "reference": "InsurancePlan/19898FL0340001-01" } }, { "name": "reason", "valueCodeableConcept": { "coding": [ { "system": "https://haau3.com/fhir/CodeSystem/plan-benefits-no-network-data-reason", "code": "marketplace", "display": "Marketplace plan" } ], "text": "A Marketplace plan's insurer publishes its provider directory itself, outside the CMS National Provider Directory, so the directory holds no network data for it. The plan's own directory is given beside this answer." } }, { "name": "directory", "valueUrl": "https://avmed.healthsparq.com/healthsparq/public/#/one/insurerCode=AVMED_I&brandCode=AVMED" }, { "name": "provider", "part": [ { "name": "npi", "valueIdentifier": { "system": "http://hl7.org/fhir/sid/us-npi", "value": "1992700001" } }, { "name": "status", "valueCoding": { "system": "https://haau3.com/fhir/CodeSystem/plan-benefits-network-status", "code": "no-network-data", "display": "No network data" } } ] } ] } ``` What an answer can and cannot tell you: * **A listing is not a contract.** `in-network` says the directory lists the provider in the plan's network. Whether the provider takes the plan for a given service is for the plan and the provider to confirm. * **`not-found` never means out of network.** The directory's network data is incomplete: many networks list no one yet. A provider not found may still be in the network. * **Only the plan's own year counts.** A plan's network can change between years, so a directory record for another year is not used, and the answer says so. * **`organizations-listed` counts organizations only.** The directory cannot count the practitioners a network lists, so a network listing no organizations may still list practitioners. * **How fresh an answer is.** Each answer asks the directory, and the directory's answers are kept for up to a day, or up to an hour when nothing was found. * **All or nothing.** A lookup that fails fails the whole answer, with a `502`, `503` or `504`, never a partial list. * **Usage.** Each call counts as one Plan Benefits request; the directory lookups it makes are not counted as Provider directory requests. ## What a plan contains Every plan conforms to two profiles, and names both in `meta.profile`: * the haau3 [Plan Benefits profile](https://fhir.haau3.com/StructureDefinition-haau3-insurance-plan.html), `https://haau3.com/fhir/StructureDefinition/haau3-insurance-plan`, whose page states every rule in full; and * the [SBC InsurancePlan profile](https://hl7.org/fhir/us/insurance-card/2.0.0-202609-ballot/StructureDefinition-sbc-insurance-plan.html) of the CARIN Digital Insurance Card guide, `hl7.fhir.us.insurance-card` version `2.0.0-ballot`. In short: * **Identity and type.** `identifier` carries the plan's ids, `type` its program, `plan.type` its network type (HMO, PPO and so on) with CMS's own words, and `ownedBy` the issuer. * **The release.** An extension names the CMS release, and for each of CMS's files its address, the date CMS last changed it and its SHA-256, so you can fetch the same file and check it. * **Where it is sold.** The plan's states, searched with `coverage-area-state`. Its counties are searched with `coverage-area-county` and are not in the resource. * **Plan facts.** What each program files about a plan, such as a Marketplace plan's metal level or a Medicare Advantage plan's special needs type. * **Deductibles and out-of-pocket maximums,** in `plan.generalCost`, each scoped to a network, to one member or a family, and to medical care, drugs or both. * **Costs of services,** in `plan.specificCost`, each coded twice: the SBC benefit category and CMS's own reference. Services the SBC categories have no code for, such as lab tests, home health care, hospital observation and a visit to a nurse or physician assistant, are in an extension shaped the same way. A cost the files do not state is never written as \$0. It carries a code saying why it has no amount, such as "not in the public files" or "not stated", and a data-absent reason in place of the number. Some Medicare Advantage plans charge Original Medicare's own amount for a deductible or a hospital stay; those costs say so and carry Original Medicare's published amounts, labelled with their year. ## What the public files do not hold * **Marketplace premiums.** They depend on the member's age and where they live, and are not in these files. A Marketplace plan's premium says so. * **Plans from the state-run exchanges,** such as California's and New York's. Only plans sold through HealthCare.gov are in the Marketplace files. * **Which providers are in a network.** A plan names its provider directory; the files do not list the providers. `$in-network` asks the CMS National Provider Directory instead. * **A member's own numbers,** such as the visits used or how much of the deductible is met. * **Drug-only and dental-only plans,** and pharmacy benefits. ## Errors | Status | When | | - | - | | `400` | A parameter this service does not serve, a repeated parameter, a comma-separated list of values, a value of the wrong form, a county code that names no county, or a search with no criteria. One issue per offending parameter, naming it. For `$in-network`, a missing `npi`, an NPI that is not ten digits, or more than 20 NPIs | | `404` | No plan with that id in the releases being served, or a plan that is not a medical plan. For `$counties`, a code that names no state, district or territory | | `410` | A later page of a search, after the plans were updated | | `502` | The plans could not be read, or, for `$in-network`, the directory failed a lookup or answered in a way that could not be read with confidence. Never reported as an empty result, a missing plan or a provider not found | | `503` | No release is being served for the program a request needs, or, for a county search, where its plans are sold is not loaded yet. The `OperationOutcome` names the program. For `$in-network`, also the directory asking for fewer requests, which carries `Retry-After` | | `504` | The plans, or for `$in-network` the directory, did not answer in time | The statuses every service shares, among them `401`, `405`, `406`, `415`, `429` and `500`, are on [Errors, limits and request ids](/errors), which also says when to send a `502`, `503` or `504` again, including inside a batch. ## Data source and licensing The plans are read from public files the Centers for Medicare & Medicaid Services publishes, a work of the U.S. Government: the PBP Benefits files and the Health Insurance Exchange Public Use Files, its Service Area file included. County names and codes are the U.S. Census Bureau's 2020 county codes, also a work of the U.S. Government. `$in-network` reads network listings from the CMS National Provider Directory when asked. CMS does not endorse haau3. # Provider directory Source: https://docs.haau3.com/provider-directory/provider-directory Search CMS's National Provider Directory as FHIR R4: practitioners and their roles, organizations, payers, insurance networks and their members, services, locations and endpoints. Look up a practitioner by NPI or by name, find the roles practitioners hold by organization, location or specialty, list the insurance networks and payers the directory holds, and see which organizations a network lists. You get back FHIR resources from the national provider directory, exactly as it publishes them. Every parameter this service serves is declared in its `CapabilityStatement`. If you send one it does not serve, you get a `400` naming it, so a search never comes back quietly unfiltered. ## Service base ``` https://api.haau3.com/v1/fhir/r4 ``` 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: ``` Content-Type: application/fhir+json; fhirVersion=4.0 ``` 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](/errors) 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](/terminology/terminology-service), [Questionnaires](/questionnaires/questionnaires), [Plan Benefits](/plan-benefits/plan-benefits) and [MedList](/medlist/medlist) use this same base and the same `CapabilityStatement`. ## What is served Every type below can be searched and read by id. | Resource | A search names | Narrow it with | Bring along | | - | - | - | - | | `Practitioner` | `identifier` (NPI), `name`, `family`, `given` | `address-city`, `address-state`, `active` | `_revinclude=PractitionerRole:practitioner` | | `PractitionerRole` | `practitioner`, `organization`, `location`, `endpoint` | `specialty`, `new-patient`, `active`; `network` and `new-patient-from-network` beside one of `practitioner`, `organization`, `location` or `specialty` | `_include` of the practitioner, organization, location, network or endpoint | | `Organization` | `name`, `identifier` (NPI), `partof`, `type=ntwk`, `type=pay` | other `type` codes, `active`, `address-city`, `address-state`, `address-postalcode` | `_include=Organization:partof`, and `_revinclude` of roles, affiliations, locations, endpoints and child organizations | | `OrganizationAffiliation` | `network`, `primary-organization`, `participating-organization` | `active` | `_include` of the participating organization, primary organization or network | | `HealthcareService` | `location` | `network` (beside `location`), `active` | | | `Location` | `organization`, `name` | `address-city`, `address-state`, `address-postalcode`, `status` | `_include=Location:organization`, `_revinclude=PractitionerRole:location` | | `Endpoint` | `organization` | `connection-type`, `status`, `name` | `_include=Endpoint:organization` | Every type also takes `_id`, `_count`, `page` and `_summary=count`. A search needs at least one parameter from the "A search names" column (or an `_id`, or a `page` cursor). The "Narrow it with" parameters filter a search but cannot be one on their own, since "every cardiology role in the country" is the whole directory with a smaller lid on it. A search with only those returns a `400` that names what is missing. `GET [base]/metadata` returns the `CapabilityStatement`, with every parameter and its definition. Anything missing from it is refused by name. ## Look up an NPI ```bash theme={null} curl -H "Authorization: Bearer $HAAU3_API_KEY" \ "https://api.haau3.com/v1/fhir/r4/Practitioner?identifier=1093209884" ``` You get a `searchset` `Bundle`. Resources come back exactly as the directory publishes them. haau3 does not rewrite, merge or enrich them. ### Identifier systems Any of these work, on `Practitioner` and on `Organization`: * `1093209884`, a bare 10-digit NPI * `http://terminology.hl7.org/NamingSystem/npi|1093209884` * `http://hl7.org/fhir/sid/us-npi|1093209884`, the system US Core uses * `urn:oid:2.16.840.1.113883.4.6|1093209884`, the registered OID They all resolve the same provider. Responses carry the directory's own system, `http://hl7.org/fhir/sid/us-npi`, so the system you send may differ from the one you get back. Rewriting it on the way out would claim the directory published something it did not. Any other identifier system returns a `400` naming it. A system the directory does not index comes back with zero results, which looks the same as "no such provider", so we refuse the request instead of guessing. One NPI per search. Use a [batch Bundle](#several-lookups-in-one-request) to look up several. ## Search by name Use `family` and `given` for the separate parts, or `name` to match across the whole name: ```bash theme={null} curl -H "Authorization: Bearer $HAAU3_API_KEY" \ "https://api.haau3.com/v1/fhir/r4/Practitioner?family=fung&given=brian" ``` A common surname returns a lot of people, so narrow it with `address-city` or `address-state`: ```bash theme={null} curl -H "Authorization: Bearer $HAAU3_API_KEY" \ "https://api.haau3.com/v1/fhir/r4/Practitioner?family=smith&address-city=Boston&address-state=MA" ``` **A place filter matches any of the practitioner's addresses.** Practitioners often have several, and one we measured had 14 across three states. `address-state=MA` means "has at least one address in Massachusetts", so a result can legitimately list addresses elsewhere. If you show "their address" in a UI, pick the one you filtered for rather than the first in the list. ## Practitioner roles A `PractitionerRole` is one practitioner's work at one organization: the specialty, the locations, and whether they accept new patients. Add `_revinclude` to a practitioner search to get the roles in the same bundle: ```bash theme={null} curl -H "Authorization: Bearer $HAAU3_API_KEY" \ "https://api.haau3.com/v1/fhir/r4/Practitioner?identifier=1093209884&_revinclude=PractitionerRole:practitioner" ``` Or search roles directly, by practitioner, organization, location or endpoint, and narrow by specialty: ```bash theme={null} curl -H "Authorization: Bearer $HAAU3_API_KEY" \ "https://api.haau3.com/v1/fhir/r4/PractitionerRole?organization={organization-id}&specialty=207RC0000X&_include=PractitionerRole:practitioner" ``` * **`specialty`** is a [NUCC Health Care Provider Taxonomy](https://taxonomy.nucc.org/) code, bare or as `http://nucc.org/provider-taxonomy|207RC0000X`. Any other code system is refused, because the directory codes specialties in NUCC alone and would answer another system with nothing. * **`new-patient`** is `newpt`, `nopt`, `existptonly` or `existptfam`, from `http://terminology.hl7.org/CodeSystem/accepting-patients`. * **`_include`** brings the records a role points to: `PractitionerRole:practitioner`, `PractitionerRole:organization`, `PractitionerRole:location`, `PractitionerRole:network` or `PractitionerRole:endpoint`. Repeat the parameter to bring several. Included entries are marked `search.mode: include`. They do not count toward `_count`. Resource ids are opaque. Take them from search results. Do not build one out of an NPI or any other identifier, since the id format belongs to the directory and can change. ## Organizations, payers and networks Search organizations by name, NPI, or the organization they are part of (`partof`), and by type from `http://terminology.hl7.org/CodeSystem/organization-type`: | `type` | Finds | | - | - | | `prov` | Healthcare providers. Narrows a search; cannot stand alone. | | `pay` | Payers. Can stand alone, to list every payer. | | `ntwk` | Insurance networks. Can stand alone, to list every network. Also accepted as `http://hl7.org/fhir/us/davinci-pdex-plan-net/CodeSystem/OrgTypeCS\|ntwk`, Plan-Net's form. | ```bash theme={null} curl -H "Authorization: Bearer $HAAU3_API_KEY" \ "https://api.haau3.com/v1/fhir/r4/Organization?type=ntwk&name=BSC" ``` **How networks are found.** The directory records a network's type as the text `ntwk` with no code, so a plain token search would find none. This service searches that text for you and checks that every organization it returns is a network. If the directory ever changes how it records networks, the search answers `502` rather than serving something that is not a network. **An NPI search can return a parent organization.** Some organizations in the directory list the NPIs of several practices. A search for one of those NPIs returns the practice and the parent, because both carry the NPI. Use `partof` or `_include=Organization:partof` to see how they relate. `_revinclude` brings the records that point at each organization: `PractitionerRole:organization`, `OrganizationAffiliation:primary-organization`, `OrganizationAffiliation:participating-organization`, `Location:organization`, `Endpoint:organization` or `Organization:partof`. ## Network membership `OrganizationAffiliation` says which organizations take part in which networks. List a network's members, or ask about one organization: ```bash theme={null} curl -H "Authorization: Bearer $HAAU3_API_KEY" \ "https://api.haau3.com/v1/fhir/r4/OrganizationAffiliation?network=Organization-ea579d05-454e-4359-8751-900c940a599a&participating-organization={organization-id}" ``` One entry means the directory lists that organization in that network. For a network's affiliations, the primary organization is the network itself. Roles can carry networks too, through `PractitionerRole?network=`. **`network` cannot be used on its own:** send it beside `practitioner`, `organization`, `location` or `specialty`. That is the directory's own rule, and this service applies it before asking, so a lone `network` is a `400`. `new-patient-from-network` follows the same rule. On `HealthcareService`, `network` needs a `location` beside it. **Not found is not out of network.** The directory's network data is sparse: only some networks list their members, and few practitioner roles name a network at all. An empty answer means the network's records in the directory do not list that provider. It does not show that the provider is out of network. **Some network searches are slow.** A network search beside a practitioner or an organization answers in under a second. Beside a specialty, or a location with many roles, the directory can take 10 to 15 seconds. This service waits up to 15 seconds for one answer and answers `504` after that, without asking again. ## Services, locations and endpoints * **`HealthcareService`**: search by `location`, or by `network` beside a `location`. The directory's service records name a network and, for a few, a location; none names an organization, so `organization` is refused rather than answered with nothing. * **`Location`**: search by managing `organization` or `name`, narrowed by address or `status`. `_revinclude=PractitionerRole:location` brings the roles held there. * **`Endpoint`**: search by managing `organization`, narrowed by `connection-type` (for example `hl7-fhir-rest`), `status` or `name`. ## Counting `_summary=count` answers how many records match, as `total`, with no entries: ```bash theme={null} curl -H "Authorization: Bearer $HAAU3_API_KEY" \ "https://api.haau3.com/v1/fhir/r4/OrganizationAffiliation?network=Organization-ea579d05-454e-4359-8751-900c940a599a&_summary=count" ``` It cannot be combined with `_count`, `page`, `_include` or `_revinclude`. Ordinary searches carry a `total` when the directory sends one, and it does on some searches and not others. A `total` is always the directory's own count; this service never computes one. ## Paging `_count` sets the page size, from 1 to 100. Anything larger returns a `400` rather than a quietly reduced page, so you always get the page size you asked for. For the next page, follow the `next` link exactly as it is given: ```json theme={null} { "resourceType": "Bundle", "type": "searchset", "link": [ { "relation": "self", "url": "https://api.haau3.com/v1/fhir/r4/Practitioner?..." }, { "relation": "next", "url": "https://api.haau3.com/v1/fhir/r4/Practitioner?...&page=..." } ] } ``` The `page` cursor is opaque and it expires. Do not build or edit one. An expired cursor returns a `400` telling you to run the search again. **Includes have a limit.** The directory returns at most 100 records for a `_revinclude` on one page. A page with more is a `502` that says so, rather than a page that quietly leaves some out. Ask for a smaller `_count`, or search the included type directly: for example `PractitionerRole?organization={organization-id}` rather than `Organization?_id={organization-id}&_revinclude=PractitionerRole:organization`. ## Several lookups in one request POST a batch `Bundle` to the base with one entry per lookup: ```bash theme={null} curl -X POST -H "Authorization: Bearer $HAAU3_API_KEY" \ -H "Content-Type: application/fhir+json" \ https://api.haau3.com/v1/fhir/r4 \ -d '{ "resourceType": "Bundle", "type": "batch", "entry": [ { "request": { "method": "GET", "url": "Practitioner?identifier=1093209884" } }, { "request": { "method": "GET", "url": "Practitioner?identifier=1194088559" } } ] }' ``` Entries run independently. One that fails does not affect the others, and each carries its own status in the `batch-response`. You can mix directory searches and terminology operations in the same batch, since both run on this base. A batch carries at most 100 entries in at most 1 MiB, and has 60 seconds: an entry still running then answers `504` inside the batch's `200`. See [limits](/errors#limits). ## Errors Every error is a FHIR `OperationOutcome`. | Status | Meaning | | - | - | | `400` | A parameter, identifier system, code system or value this service does not serve; a search with nothing in the "A search names" column; `network` without its companion; or an expired or invalid `page` cursor. The outcome names the parameter. | | `404` | No such resource, or no such interaction on this base. | | `502` | The directory could not be reached, answered in a shape that cannot be served, or answered with more than is served at once: a `_revinclude` past 100 records on a page, or an answer over 8 MiB. | | `503` | The directory is rate limiting requests. Wait the number of seconds in `Retry-After`, then send the request again. | | `504` | The directory did not respond within 15 seconds. | The statuses every service shares, among them `401`, `405`, `406`, `415`, `429` and `500`, are on [Errors, limits and request ids](/errors), which also says when to send a `502`, `503` or `504` again, including inside a batch. **An empty result means the directory held no matching record when it was asked, at most an hour earlier.** It never means the request failed: if we cannot get an answer you get a `5xx` saying so, so code that branches on an empty `searchset` can rely on that branch. For a network search, an empty result means no record in the directory lists that membership. It is not a statement that a provider is out of network. Search parameters work the same way. Anything this service does not serve is refused by name instead of ignored, so a search that returns results applied every filter you sent. Some parameters the directory knows are refused with the reason: for example `role` on `PractitionerRole`, because no role in the directory carries a role code, so the filter could only match nothing. ## Data source and licensing Provider data comes from the **CMS National Provider Directory**, a public federal directory, and is served as published. It carries no attribution requirements for you, and haau3 adds none. Answers are read from the directory live. A repeated request may be answered from a cache: a result up to a day old, an empty result up to an hour old. Every record carries the date the directory last loaded it in `meta.lastUpdated`. Provider identity is public information. This API handles no patient data, and request parameter values are not logged. # Questionnaires Source: https://docs.haau3.com/questionnaires/questionnaires Fetch standard health questionnaires as FHIR R4, render them, and build a standard QuestionnaireResponse in your own application. Fetch a standard health questionnaire as a FHIR `Questionnaire`, render it with your own components, and build a `QuestionnaireResponse` in your own application. The forms are served exactly as their source publishes them, down to the order of the answer options. Nothing about a respondent is sent here. This service publishes forms; it accepts no answers and stores no responses. ## Service base ``` https://api.haau3.com/v1/fhir/r4 ``` 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: ``` Content-Type: application/fhir+json; fhirVersion=4.0 ``` 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](/errors) 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](/terminology/terminology-service), the [Provider directory](/provider-directory/provider-directory), [Plan Benefits](/plan-benefits/plan-benefits) and [MedList](/medlist/medlist) use this same base and the same `CapabilityStatement`. ## What is served | Resource | Interactions | | - | - | | `Questionnaire` | search by `url` or `version`, read | `GET [base]/metadata` returns the `CapabilityStatement`. It lists everything this service supports, and anything missing from it is refused by name. This service holds copies of five questionnaires, all from LOINC release 2.83. These are the forms it can always answer for, and they render the same way on every request: | Title | Canonical URL | Code | Version | | - | - | - | - | | PHQ-9 quick depression assessment panel | `http://loinc.org/q/44249-1` | LOINC 44249-1 | `2.83` | | Alcohol use disorder identification test-consumption | `http://loinc.org/q/72109-2` | LOINC 72109-2 | `2.83` | | Generalized anxiety disorder 7 item | `http://loinc.org/q/69737-5` | LOINC 69737-5 | `2.83` | | Pain intensity, Enjoyment of life, General activity (PEG) 3 item pain scale | `http://loinc.org/q/91148-7` | LOINC 91148-7 | `2.83` | | Edmonton Symptom Assessment System | `http://loinc.org/q/112503-8` | LOINC 112503-8 | `2.83` | Each carries its own `copyright`, which must be kept with the form, and the PHQ-9's and GAD-7's carry a third-party notice inside it. See [Data source and licensing](#data-source-and-licensing). Naming any other canonical `url` reaches the questionnaire directly from LOINC. Those are not held here, so they depend on LOINC being reachable when you ask. A canonical that is the address of a form published at its own address is fetched from that address, when it sits under a place this service is set up to reach. One such place is `https://smart-health-checkin.org/connectathon/Questionnaire/`, where the SMART Health Check-in connectathon publishes its forms. A form fetched this way is served exactly as published, with its own copyright, and its entry's `fullUrl` is the address it was fetched from. The same `5xx` answers apply when it cannot be reached. Any other canonical matches nothing. To have forms you publish reached this way, email [hello@haau3.com](mailto:hello@haau3.com) with the address they are published under. ## List the questionnaires held here A search with no parameters lists the questionnaires this service holds copies of. That list is what you can rely on: those forms answer from copies held here, so they do not depend on LOINC being reachable. The set grows over time. ```bash theme={null} curl -H "Authorization: Bearer $HAAU3_API_KEY" \ "https://api.haau3.com/v1/fhir/r4/Questionnaire" ``` You get a `searchset` Bundle with `total` set. To reach a questionnaire that is not on this list, name its canonical `url`. ## Fetch by canonical URL ```bash theme={null} curl -G -H "Authorization: Bearer $HAAU3_API_KEY" \ --data-urlencode "url=http://loinc.org/q/44249-1" \ "https://api.haau3.com/v1/fhir/r4/Questionnaire" ``` Add the version to pin it. `{url}|{version}` matches that version only and never comes back as a different version. For a questionnaire held here, a version that is not the one held comes back as an empty `searchset`: ```bash theme={null} curl -G -H "Authorization: Bearer $HAAU3_API_KEY" \ --data-urlencode "url=http://loinc.org/q/44249-1|2.83" \ "https://api.haau3.com/v1/fhir/r4/Questionnaire" ``` `version` also works on its own: `?version=2.83`. On its own it filters the questionnaires held here; alongside `url` it names the LOINC release to fetch. ## Reach a questionnaire that is not held here Name any canonical LOINC publishes. The HHS panel on race, ethnicity, sex, primary language and disability, for example, is not one of the five held here: ```bash theme={null} curl -G -H "Authorization: Bearer $HAAU3_API_KEY" \ --data-urlencode "url=http://loinc.org/q/69919-9" \ "https://api.haau3.com/v1/fhir/r4/Questionnaire" ``` It comes back the same shape as a held one, with the same `id` policy and its own `copyright` intact. Once you know its id you can read it directly: `GET [base]/Questionnaire/69919-9`. Pinning works too, across LOINC releases rather than only the one held here: ```bash theme={null} curl -G -H "Authorization: Bearer $HAAU3_API_KEY" \ --data-urlencode "url=http://loinc.org/q/69919-9|2.80" \ "https://api.haau3.com/v1/fhir/r4/Questionnaire" ``` Two differences from a questionnaire held here, both worth designing around: * **It depends on LOINC being reachable.** When LOINC cannot answer, you get a `502`, `503` or `504`, never an empty `searchset`. A questionnaire held here is never affected. * **Its shape is not covered by [What the resources contain](#what-the-resources-contain).** It may use item types the held forms do not. A version LOINC does not publish comes back as a `400` naming `version`, because versions name a release of LOINC's catalogue rather than a field on the form. ## Read by id Ids are the questionnaire's LOINC number, whether it is held here or reached from LOINC. ```bash theme={null} curl -H "Authorization: Bearer $HAAU3_API_KEY" \ "https://api.haau3.com/v1/fhir/r4/Questionnaire/44249-1" ``` The resource `id` is assigned by this service. Everything else in the resource is the source's, unchanged. `meta.versionId` and `meta.tag` are not carried, since no version read or history is served; `meta.lastUpdated` is the source's own and is passed through. ## Only `url` and `version` filter Every other search parameter is refused with a `400` that names it. That includes `status`, `identifier`, `title`, `publisher`, `name`, `code`, `experimental`, `_count`, `_summary` and `page`. A search never comes back quietly unfiltered. `_count` and `page` are refused because there is nothing to page: every search returns all its matches in one page with `total` set. **`status` is not a filter here, and every questionnaire held here is `draft`.** That is the value their source publishes (measured 2026-10-10, LOINC 2.83), and this service does not edit content. A `status=active` filter would have matched nothing, which reads as "there are no such forms", so it is refused by name instead. Do not gate your code on `status` being `active`. ## What the resources contain This section describes **the five questionnaires held here**. A questionnaire reached by canonical `url` may be shaped differently, and the numbers below say how differently. Every held questionnaire is a flat list of questions. Measured 2026-10-10 against LOINC 2.83: * Question types are `choice` and `decimal` only. No groups, no display items, no nesting. * Answers are inline `answerOption.valueCoding`. There is no `answerValueSet`, so rendering a form needs no terminology call. * **Answer options carry no scores or weights**, and this service computes none. The final `decimal` item on each questionnaire, a total or a mean score, is an ordinary number field, not a computed one. * There is no `enableWhen`, no `initial`, no `readOnly` and no extension. Nothing is conditional. * Options arrive in the source's own order. Render them in that order: it is part of the resource, and reordering them changes what the form asks. A future release of the source may change any of this. If you render generically, handle a question type you do not recognise by saying so rather than skipping it. **A questionnaire reached by `url` is not bound by the list above.** Of 72 LOINC questionnaires sampled by canonical on 2026-09-08, 32 matched that shape and 40 did not. Present across the sample: `string` on 33 forms, `group` on 14, `date` on 7 and `time` on 1; 14 forms nested, to a depth of 5; 6 used `repeats`. No sampled form used `enableWhen` or `answerValueSet`. If you render a form you did not choose in advance, handle an item type you do not support by saying so rather than skipping it. The response builder below already works this way: it sorts items into supported and unsupported and names a reason for each one it will not answer. ## Build a QuestionnaireResponse Build it in your own application. This service accepts no `QuestionnaireResponse` and stores none, so answers stay where they were given. The rules that matter: * `questionnaire` is the version-specific canonical, `{url}|{version}`. * `status` is `in-progress` while answers are being collected, `completed` when they are done. * Include one `item` per question, mirroring `linkId` and `text`. * A `choice` answer is one of the question's own `answerOption.valueCoding` values, copied as the questionnaire published it. * `authored` is when the answers were last changed. ```json theme={null} { "resourceType": "QuestionnaireResponse", "questionnaire": "http://loinc.org/q/72109-2|2.83", "status": "completed", "authored": "2026-09-08T10:05:00.000Z", "item": [ { "linkId": "72043", "text": "How often do you have a drink containing alcohol?", "answer": [ { "valueCoding": { "system": "http://loinc.org", "code": "LA6270-8", "display": "Never" } } ] }, { "linkId": "72044", "text": "How many standard drinks containing alcohol do you have on a typical day?", "answer": [ { "valueCoding": { "system": "http://loinc.org", "code": "LA15694-5", "display": "1 or 2" } } ] }, { "linkId": "72042", "text": "How often do you have 6 or more drinks on 1 occasion?", "answer": [ { "valueCoding": { "system": "http://loinc.org", "code": "LA6270-8", "display": "Never" } } ] }, { "linkId": "72040", "text": "Total score" } ] } ``` ## Several in one request POST a FHIR `Bundle` with `type: batch` to the service base. Entries are independent, and a batch can mix questionnaires with terminology and directory calls. A batch carries at most 100 entries in at most 1 MiB, and has 60 seconds: an entry still running then answers `504` inside the batch's `200`. See [limits](/errors#limits). ```bash theme={null} curl -X POST "https://api.haau3.com/v1/fhir/r4" \ -H "Authorization: Bearer $HAAU3_API_KEY" \ -H "Content-Type: application/fhir+json" \ -d '{ "resourceType": "Bundle", "type": "batch", "entry": [ { "request": { "method": "GET", "url": "Questionnaire/44249-1" } }, { "request": { "method": "GET", "url": "Questionnaire/72109-2" } } ] }' ``` ## Errors Every error is a FHIR `OperationOutcome`. | Status | Meaning | | - | - | | `400` | A search parameter this service does not serve, a repeated parameter, a comma-separated list of values, or an id that is not a valid FHIR id. The outcome names it. | | `404` | No questionnaire with that id, or no such interaction. Operations, history and version reads are not served. | | `502` | LOINC or a form's publisher could not be reached, or answered with something this service will not pass on. | | `503` | LOINC or a form's publisher is throttling requests, LOINC is not configured on this deployment, or the places this service fetches forms from could not be read. A throttled answer carries `Retry-After`. | | `504` | LOINC or a form's publisher did not answer in time. | The statuses every service shares, among them `401`, `405`, `406`, `415`, `429` and `500`, are on [Errors, limits and request ids](/errors), which also says when to send a `502`, `503` or `504` again, including inside a batch. **An empty `searchset` means no questionnaire matched.** It never means the request failed. If a request cannot be answered you get a `502`, `503` or `504` carrying an `OperationOutcome`, so a failure never arrives looking like an empty result. The `5xx` rows only ever apply to a canonical this service holds no copy of. The five questionnaires listed above are answered from copies held here, and no call is made for them. No operations are offered on `Questionnaire`, `$populate` included. Populating a form means sending patient data to whoever populates it, and this service holds none. ## Data source and licensing Questionnaire content comes from **LOINC**, published by the Regenstrief Institute, and is served as published. A form fetched from its own address comes from its publisher, and is served as that publisher published it, with its own `copyright`. This material contains content from LOINC ([http://loinc.org](http://loinc.org)). LOINC is copyright © Regenstrief Institute, Inc. and the Logical Observation Identifiers Names and Codes (LOINC) Committee and is available at no cost under the license at [http://loinc.org/license](http://loinc.org/license). LOINC® is a registered United States trademark of Regenstrief Institute, Inc. **Each questionnaire carries its own `copyright` element, and it must be kept with the form.** It is not decoration: for the PHQ-9 and the GAD-7 it carries a third-party notice as well as LOINC's, and the licence requires that notice to travel with the content wherever it goes. Keep the `copyright` element intact when you store, display or forward a questionnaire. The PHQ-9 and GAD-7 notice reads: "Copyright © Pfizer Inc. All rights reserved. Developed by Drs. Robert L. Spitzer, Janet B.W. Williams, Kurt Kroenke and colleagues, with an educational grant from Pfizer Inc. No permission required to reproduce, translate, display or distribute." The AUDIT-C, the PEG and the ESAS carry LOINC's notice only. The AUDIT is a World Health Organization instrument that WHO publishes for free reproduction; check WHO's own terms for your use of it. This API handles no patient data. Request parameters are not logged. # Quickstart Source: https://docs.haau3.com/quickstart Create a free API key and make your first authenticated call to the Scheduling API. Every request needs a free API key, sent as a Bearer token. Requests without a valid key return `401`; requests with one return `200`. This guide covers getting a key and making your first call to `GET /v1/scheduling/slots`. ## 1. Create an organization and key Go to [platform.haau3.com](https://platform.haau3.com) and sign in. The platform is organization-based, so you'll create or join an organization. Your keys belong to it. A new organization starts on the Developer plan, which is free. Open **API Keys** and create a key. It looks like `haau3_sk_…` and is shown only once, so copy it somewhere safe. ## 2. Make your first call Pass the key as a Bearer token. A request without a valid key returns `401`: ```bash Without a key → 401 theme={null} curl -i https://api.haau3.com/v1/scheduling/slots ``` ```json Response theme={null} HTTP/2 401 { "type": "https://docs.haau3.com/errors#unauthorized", "title": "API key missing or not recognised", "status": 401, "detail": "An API key is required. Create a free key in the dashboard. See https://docs.haau3.com/authentication", "instance": "urn:uuid:0b6f2d4e-8c1a-4e3b-9f5d-2a7c6e1b4d90" } ``` Add your key, here from the `HAAU3_API_KEY` environment variable, and a search returns `200` with matching slots. Each [SDK](/sdks) reads the same variable: ```bash cURL theme={null} curl "https://api.haau3.com/v1/scheduling/slots?publisher=haau3&limit=1" \ -H "Authorization: Bearer $HAAU3_API_KEY" ``` ```ts TypeScript theme={null} import { Haau3 } from "@haau3/sdk"; const haau3 = new Haau3(); // reads HAAU3_API_KEY const found = await haau3.searchSlots({ publisher: "haau3", limit: 1 }); console.log(found.total); ``` ```python Python theme={null} from haau3 import Haau3 client = Haau3() # reads HAAU3_API_KEY found = client.search_slots(publisher="haau3", limit=1) print(found["total"]) ``` ```go Go theme={null} package main import ( "context" "fmt" "log" "github.com/haau3/haau3-go" ) func main() { ctx := context.Background() client, err := haau3.NewClient() // reads HAAU3_API_KEY if err != nil { log.Fatal(err) } found, err := client.SearchSlots(ctx, haau3.SearchSlotsParams{Publisher: new("haau3"), Limit: new(1)}) if err != nil { log.Fatal(err) } fmt.Println(found.Total) } ``` ```json Response theme={null} { "total": 699, "slots": [ { "id": "slot-haau3-psych-20261018-1700", "publisher": "haau3", "scheduleId": "sched-haau3-psych", "status": "free", "start": "2026-10-18T21:00:00.000Z", "end": "2026-10-18T21:50:00.000Z", "serviceTypeCode": "124", "serviceTypeText": "General Practice", "serviceTypes": [ { "system": "http://terminology.hl7.org/CodeSystem/service-type", "code": "124", "display": "General Practice" } ], "acceptingPatients": "newpt", "isVirtual": true, "bookingPhone": "+1-617-555-0100", "location": { "id": "loc-haau3-telehealth", "identifier": [ { "system": "https://haau3.com/synthetic/location", "value": "loc-haau3-telehealth" } ], "name": "haau3 Telehealth", "phone": [ "+1-617-555-0199" ], "website": [ "https://haau3.com/telehealth" ], "physicalType": { "system": "http://terminology.hl7.org/CodeSystem/location-physical-type", "code": "vi" } }, "provider": { "id": "prac-haau3-psych", "identifier": [ { "system": "https://haau3.com/synthetic/practitioner", "value": "prac-haau3-psych" } ], "type": "practitioner-role", "name": "Dr. Sarah Whitefeather", "specialty": "Psychiatry Physician", "specialties": [ { "system": "http://nucc.org/provider-taxonomy", "code": "2084P0800X", "display": "Psychiatry Physician" } ] }, "source": { "publisher": "haau3", "resourceId": "slot-haau3-psych-20261018-1700", "transactionTime": "2026-10-18T00:00:00.000Z", "syncedAt": "2026-10-18T12:01:36.474Z" } } ] } ``` `total` is the full pre-pagination count. Page through results with `limit` (1 to 100, default 25) and `offset`. Results start from now. To include slots that already started, add `includePast=true`. An explicit `startAfter` applies as given. ## 3. Filter the results The endpoint supports query filters. For example, to get only virtual slots in Massachusetts: ```bash theme={null} curl -s "https://api.haau3.com/v1/scheduling/slots?state=MA&isVirtual=true&limit=10" \ -H "Authorization: Bearer haau3_sk_your_key_here" ``` To discover the valid `serviceTypeCode` values, call `GET /v1/scheduling/service-types`, which returns the distinct service types seen across served slots, each with a count. To find one clinician's slots, add `npi` with their ten-digit National Provider Identifier. To narrow by specialty, add `specialty`. Specialty codes are each publisher's own, and `GET /v1/scheduling/specialties` lists the ones in use. Each of these filters, and `serviceTypeCode`, takes a comma-separated list and matches any of its values. To find slots accepting new patients, add `acceptingPatients=newpt` (HL7 accepting-patients codes: `newpt` / `existptonly` / `existptfam`). The filter is lenient: a slot that does not declare an acceptance code still matches: ```bash theme={null} curl -s "https://api.haau3.com/v1/scheduling/slots?acceptingPatients=newpt&limit=10" \ -H "Authorization: Bearer haau3_sk_your_key_here" ``` See the [API Reference](/api-reference) for every parameter, and [Authentication](/authentication) for more on keys. For the FHIR base, start with the [Terminology service](/terminology/terminology-service) for codes and value sets, or with [MedList](/medlist/medlist) to reconcile one patient's medication records. # Slot Publisher Source: https://docs.haau3.com/scheduling/slot-publisher haau3's reference SMART Scheduling Links feed: an openly fetchable $bulk-publish publisher you can connect to directly while evaluating the Scheduling API. Alongside the [Scheduling API](/scheduling/smart-scheduling-links), which plays the **Slot Aggregator** role, haau3 also operates a small **Slot Publisher**: its own [SMART Scheduling Links](https://build.fhir.org/ig/HL7/smart-scheduling-links) `$bulk-publish` feed of provider availability. ## A reference feed for the open standard The Slot Publisher is primarily a **contribution to the open SMART Scheduling Links standard**: a real reference feed that publishers, clients, and tooling can test against. It is **not** a billed platform API and is **not** part of the B2B product surface. The product is the **aggregator** (the [Scheduling API](/scheduling/smart-scheduling-links)). Because an SSL feed is meant to be openly fetchable, the publisher requires **no API key**. The `/v1/scheduling/*` aggregator endpoints are key-authenticated. ## Why it's hosted on the platform Hosting the feed at `api.haau3.com` lets any organization **evaluating the haau3 Scheduling API** test a **direct connection to a real Slot Publisher** with zero setup: point your own client, ingestion worker, or public SSL tooling at a single URL instead of standing up a feed or integrating a third party. It's the simplest way to see the publisher → aggregator → client flow end to end. Our own aggregator ingests this same feed, so what you fetch directly here is exactly what flows into `GET /v1/scheduling/slots` and `GET /v1/scheduling/provider-locations` under the `haau3` publisher slug. You can compare the two sides: the raw publisher feed, and the normalized, unified result the aggregator serves. ## Endpoints Both are public (no API key) and serve non-PHI synthetic data. * `GET https://api.haau3.com/scheduling/$bulk-publish`: the `$bulk-publish` manifest (`transactionTime` + an `output[]` array of absolute NDJSON file URLs). * `GET https://api.haau3.com/scheduling/{Resource}.ndjson`: one newline-delimited JSON file per resource type (`Location`, `Practitioner`, `PractitionerRole`, `HealthcareService`, `Schedule`, `Slot`), served as `application/fhir+ndjson`. The feed changes once a day, at 00:00 UTC, when its `transactionTime` moves to the start of the new day. Every response carries `Cache-Control: max-age` set to the seconds left until then, which SMART Scheduling Links reads as how long to wait before polling again. ```bash theme={null} # Read the manifest, then fetch the slots it lists. No key required. curl https://api.haau3.com/scheduling/\$bulk-publish curl https://api.haau3.com/scheduling/Slot.ndjson ``` ## What's in the feed A small, curated dataset built to exercise representative SSL scenarios so you can see how each maps through the aggregator: * **A rolling window**: slots for the 90 days starting today (by the UTC date), at the practices' local times in US Eastern time, so the feed never runs out of future availability. A slot keeps its `id` and content for every day it is in the window. * **Virtual vs in-person** appointments (`Location.physicalType`, coded from the SMART Scheduling Links value set: `vi` for the telehealth location, which has no address, and `ph` for the two clinics). * **Accepting-patients** states in the `patient-type` extension on every `Slot`: new patients, existing patients only, existing patients and families, and a weekly not-accepting example (the aggregator stores it but never serves it). * **Multiple service types and provider specialties** (HL7 service-type and NUCC codes). * **A healthcare service as the provider**: the immunization clinic's schedule names a `HealthcareService` and no practitioner. * **Location details**: coordinates (`position`) and a `description` on the two clinics. * **Booking deep links and phone numbers** (SSL booking extensions, the phone as a `ContactPoint`). Nothing can be booked here: a slot's booking link opens this page. The feed follows the SMART Scheduling Links 1.0.0 build of October 6, 2026, with one exception: three of its four practitioners carry two identifiers, a National Provider Identifier and a haau3 identifier, where the build allows one. It passes the same base-FHIR and profile checks the aggregator applies on ingest, including the stricter ones a live production feed must meet (see [SMART Scheduling Links](/scheduling/smart-scheduling-links)). All of its data is synthetic: the providers, numbers and identifiers are made up. Its National Provider Identifiers fail the NPI check digit on purpose, so they can never belong to a real clinician. The aggregator's `npi` filter still finds them, because it matches an NPI as the publisher sent it. ## How it relates to the aggregator | | Slot Publisher (this feed) | Scheduling API (aggregator) | | - | - | - | | Role | Publishes one provider's availability | Combines many publishers into unified search | | Endpoint | `/scheduling/$bulk-publish` (+ NDJSON) | `/v1/scheduling/slots` (+ `actors`, `publishers`, `service-types`, `specialties`) | | Auth | Public, no key | API key required | | Format | Raw FHIR NDJSON | Denormalized [`Slot`](/api-reference) JSON | | Purpose | Open-standard reference / direct-connection testing | The haau3 B2B product | To consume the aggregator instead, start with the [Quickstart](/quickstart) and the [SMART Scheduling Links](/scheduling/smart-scheduling-links) overview. # SMART Scheduling Links Source: https://docs.haau3.com/scheduling/smart-scheduling-links How the haau3 Scheduling API aggregates provider availability from SMART Scheduling Links publishers. The **haau3 Scheduling API** is a **Slot Aggregator** for [SMART Scheduling Links](https://build.fhir.org/ig/HL7/smart-scheduling-links) (SSL). It ingests provider availability from many SSL publishers and serves it through one key-authenticated API. A Slot Discovery Client can search every publisher by calling the Scheduling API, instead of integrating with each publisher separately. ## What SMART Scheduling Links is SMART Scheduling Links is an HL7 specification for publishing open appointment availability. A publisher exposes a `$bulk-publish` manifest that points at NDJSON files of FHIR resources: `Schedule`, `Slot`, and the actors they reference (`Location`, `Practitioner`, `PractitionerRole`, `HealthcareService`). Anyone can poll the manifest to see what's bookable and send patients to the publisher's booking link. ## The roles in SSL The IG defines a small set of roles: * **Slot Publisher**: a healthcare provider's API that advertises its available slots. * **Provider Booking Portal**: the provider's UI where a patient books a slot. * **Slot Aggregator**: a third-party API that combines data from multiple Slot Publishers and offers unified search across them. It does not own the underlying data. * **Slot Discovery Client**: the patient's booking tool. It searches for slots and sends the patient to the right portal to finish booking. The haau3 Scheduling API is a Slot Aggregator. A Slot Discovery Client queries it once and gets consistent results across every publisher the API ingests, rather than polling and reconciling each publisher's `$bulk-publish` feed itself. haau3 also operates a reference **Slot Publisher**, its own openly fetchable `$bulk-publish` feed, so you can test a direct publisher connection while evaluating the aggregator. It is a contribution to the open standard. It is not a separate product. See [Slot Publisher](/scheduling/slot-publisher). ## What the Scheduling API does as the aggregator The Scheduling API turns raw publisher feeds into one queryable model in three steps: * **Ingest**: each publisher's latest `$bulk-publish` snapshot is downloaded and tracked. * **Validate**: every resource is checked for base FHIR R4 validity, then against the SMART Scheduling Links 1.0.0 profiles (continuous build of October 6, 2026) with strict referential integrity. A publisher's live production feed must meet every element the profiles require. A feed from a publisher still testing its implementation may use the May 2026 ballot's shape where 1.0.0 tightened it: acceptance in `appointmentType` instead of the `patient-type` extension, and `booking-phone` as a string. Records that don't conform are quarantined and kept out of the served data. * **Normalize**: conforming slots are flattened into a denormalized [`Slot`](/api-reference) shape with its source `publisher`, status, start and end times, service types (every coding, with its system), the schedule's specialties, accepting-patients code, an expanded `location` (name, description, telecom, address, coordinates, physical type), `provider` (a practitioner, or a healthcare service when the schedule names no practitioner, with its coded specialties), `isVirtual`, and `source` (the publisher's own id for the slot, the publisher's snapshot time, and when the API last read it). Fields a source omits stay absent; the API adds nothing. Quarantine keeps bad data out of the API, but the haau3 team also works directly with publishers to flag nonconformant slots so issues get fixed at the source. This keeps the served data at a consistently high quality over time. ### Limits on a feed Each cycle reads a publisher's feed within these limits: | What | Limit | Past it | | - | - | - | | A line of an NDJSON file | 64 KiB | The line is left out and the rest of the file is read. A line that is not JSON is left out the same way. | | An NDJSON file | 64 MiB | The feed is not read that cycle. The last snapshot read stays served. | | A snapshot's files together | 64 MiB | The same. | | The `$bulk-publish` manifest | 1 MiB, listing at most 100 files | The same. | | Time to download | 10 seconds for the manifest, 60 seconds for each file | The same. | | A list a profile reads, such as a Location's `telecom` or a Schedule's `actor` | 100 entries | The resource is quarantined. | ## Querying availability Five endpoints serve the model. See the [API Reference](/api-reference) for full details. * `GET /v1/scheduling/slots`: search availability with filters (status, publisher, service type, provider NPI, specialty, city, state, virtual, time window, accepting-patients) and pagination. Results start from now: with no `startAfter`, a slot that started before the request is left out unless you pass `includePast=true`, and an explicit `startAfter` applies as given. Every slot carries its source `publisher`, and `publisher=` narrows results to one publisher (useful for a publisher inspecting just its own slots). `serviceTypeCode` matches a slot when any of its `serviceTypes` carries the code. `npi` matches a slot whose provider carries that National Provider Identifier (ten digits), a clinician's or an organization's, across every publisher. `specialty` matches a slot when its provider's `specialties` or its schedule's `specialties` carry the code: send `code` to match it in any code system, or `system|code` for one system. Specialty codes are each publisher's own and are not translated between code systems, so a code finds the publishers that use it, and a specialty a publisher sends as text only cannot be matched. `serviceTypeCode`, `npi` and `specialty` each take a comma-separated list of up to 100 values and match a slot carrying any of them. The `acceptingPatients` filter (HL7 accepting-patients codes `newpt`, `existptonly` and `existptfam`) is **lenient**: a slot that does not declare an acceptance code still matches any requested value (absence never hides a slot), while a slot that declares one must equal it. A slot a source marks `nopt` (not accepting patients) is **never served**: it is not real availability, so it never appears in results regardless of the filter. * `GET /v1/scheduling/provider-locations`: the matching slots grouped by provider location. One entry per provider at a location, or per location when a slot names no provider, aggregated server side, with exact counts and soonest availability over the full match set. It takes the same filters as `/slots` except `providerLocationKey`, including `includePast`. Paginated by provider location; each one's `key` returns its slots through `/slots?providerLocationKey=`. * `GET /v1/scheduling/publishers`: list the registered publishers, each with an `enabled` flag (the `slug` values accepted by the `publisher` filter). A publisher can be listed and not currently ingesting, so check `enabled` rather than assuming every slug returns slots. * `GET /v1/scheduling/service-types`: list the distinct service types observed across served slots, each with its `system` and a `count` of the slots carrying it: a discovery aid for the `serviceTypeCode` filter (the values it accepts), self-healing as publishers change. Like `/slots`, it counts slots from now unless you pass `includePast=true`, and only from enabled publishers. * `GET /v1/scheduling/specialties`: list the distinct specialties observed across served slots, from the provider's `specialties` and the schedule's, each with its `system` and a `count` of the slots carrying it: the values the `specialty` filter accepts, in each publisher's own code system. It follows the same rules as `/service-types`. All five require a valid API key (see [Authentication](/authentication)). For your first call, start with the [Quickstart](/quickstart). All five answer from live data or not at all. If the scheduling service is unavailable or slow they return **503**, an [RFC 9457 problem](/errors#unavailable) with `Retry-After`. They never fall back to sample or placeholder data, so a `200` is always a real answer. Treat a `503` as retryable; a malformed request still returns `400` with `issues` naming each parameter at fault, so the two are always distinguishable. A parameter an endpoint does not take, or one sent twice, is refused the same way: see [parameters](/errors#parameters). ## Standards status SMART Scheduling Links went to HL7 ballot in May 2026 as `1.0.0-ballot`. Its [continuous build](https://build.fhir.org/ig/HL7/smart-scheduling-links/en/), dated October 6, 2026, carries the ballot's resolutions as version 1.0.0 (STU 1). It is not yet an authorized publication. The Scheduling API validates publisher feeds against the 1.0.0 build's profiles. That build changed three things a feed carries: every `Slot` has a `patient-type` extension saying whether it is open to new or existing patients, `booking-phone` is a `ContactPoint`, and a `Location` no longer needs an `identifier` or an `address`. A feed from a publisher still testing its implementation may keep the ballot's form of the first two, as described under Validate above. SMART Scheduling Links builds on **FHIR Bulk Publish**, the mechanism for distributing static, pre-generated FHIR datasets (provider directories, formularies, scheduling, and similar) through a `$bulk-publish` manifest. Bulk Publish is part of version 4 of the HL7 [Bulk Data Access IG](https://build.fhir.org/ig/HL7/bulk-data/en/), balloted as `4.0.0-ballot` (see its [publish operation](https://hl7.org/fhir/uv/bulkdata/4.0.0-202609-ballot/publish.html)). The current published version of that IG, 3.0.0, does not include it. # SDKs Source: https://docs.haau3.com/sdks Open-source clients for TypeScript, Python and Go: every endpoint as a typed call, with retries, typed errors, paging and batches. The haau3 platform API is in preview: it may change as we build, usually to add features or improve performance. The platform API has an open-source SDK for TypeScript, Python and Go. Each is generated from the API's [OpenAPI document](https://api.haau3.com/openapi.json), so every endpoint is a method named by its operationId, with its parameters and its answer typed. Each also retries after a timeout, a network failure, a `429` or a `5xx`, gives typed errors that never print a response body, follows a FHIR search's pages, and sends several requests as one batch. | | Package | Requires | Source | | - | - | - | - | | TypeScript | [`@haau3/sdk`](https://www.npmjs.com/package/@haau3/sdk) | Node 22 or newer | [haau3/haau3-node](https://github.com/haau3/haau3-node) | | Python | [`haau3`](https://pypi.org/project/haau3/) | Python 3.10 or newer | [haau3/haau3-python](https://github.com/haau3/haau3-python) | | Go | [`github.com/haau3/haau3-go`](https://pkg.go.dev/github.com/haau3/haau3-go) | Go 1.26 or newer | [haau3/haau3-go](https://github.com/haau3/haau3-go) | All three are MIT licensed. Each runs on a server only, and refuses to start in a browser, where an API key would be public. Every operation's page in the [API reference](/api-reference) shows its call in each language beside the curl command. ## Install ```sh TypeScript theme={null} npm install @haau3/sdk ``` ```sh Python theme={null} pip install haau3 ``` ```sh Go theme={null} go get github.com/haau3/haau3-go ``` ## Authenticate Create an API key on [platform.haau3.com](https://platform.haau3.com) (see [Authentication](/authentication)). Each SDK reads it from `HAAU3_API_KEY`, or takes it as an option. Make one client and reuse it: it holds a pool of connections. ```ts TypeScript theme={null} import { Haau3 } from "@haau3/sdk"; const haau3 = new Haau3(); // reads HAAU3_API_KEY ``` ```python Python theme={null} from haau3 import Haau3 client = Haau3() # reads HAAU3_API_KEY ``` ```go Go theme={null} client, err := haau3.NewClient() // reads HAAU3_API_KEY if err != nil { return err } ``` Python also has `AsyncHaau3`, with the same methods awaited, on asyncio and on trio. ## A first call ```ts TypeScript theme={null} const plan = await haau3.readInsurancePlan("H2406-013-000"); console.log(plan.name); // "AARP Medicare Advantage from UHC FL-0021 (PPO)" ``` ```python Python theme={null} plan = client.read_insurance_plan("H2406-013-000") print(plan.get("name")) # "AARP Medicare Advantage from UHC FL-0021 (PPO)" ``` ```go Go theme={null} plan, err := client.ReadInsurancePlan(ctx, "H2406-013-000") if err != nil { return err } if plan.Name != nil { fmt.Println(*plan.Name) // "AARP Medicare Advantage from UHC FL-0021 (PPO)" } ``` A FHIR answer is FHIR R4 in each language's own form: `@types/fhir`'s types in TypeScript (exported as `fhir4`), plain dicts typed with TypedDicts from HL7's R4 schema in Python, and structs generated from the same schema in Go's `fhir` package. FHIR elements are optional, so Python reads them with `.get()` and Go holds each as a pointer. The other services' answers have the API's own types. ## One call per service ```ts TypeScript theme={null} // Scheduling: open appointment slots, here in Boston. const { slots, total } = await haau3.searchSlots({ city: "Boston", state: "MA", limit: 10 }); // Provider directory: a practitioner by NPI, from CMS's National Provider Directory. const practitioners = await haau3.searchPractitioner({ identifier: "1093209884" }); // Terminology: a LOINC code's display and properties. const glucose = await haau3.codeSystemLookup({ system: "http://loinc.org", code: "2345-7" }); // Questionnaires: a form by its id, such as the PHQ-9. const phq9 = await haau3.readQuestionnaire("44249-1"); // Plan Benefits: a Medicare Advantage or ACA Marketplace plan, and which providers are in its network. const plan = await haau3.readInsurancePlan("H2406-013-000"); const network = await haau3.checkInsurancePlanNetwork("H3359-038-000", { npi: ["1518929058"] }); // MedList: one reconciled medication list from one patient's FHIR medication records. It receives // patient data, so send it only under an executed agreement. const list = await haau3.reconcileMedications(records); ``` ```python Python theme={null} # Scheduling: open appointment slots, here in Boston. found = client.search_slots(city="Boston", state="MA", limit=10) print(found["total"], len(found["slots"])) # Provider directory: a practitioner by NPI, from CMS's National Provider Directory. practitioners = client.search_practitioner(identifier="1093209884") # Terminology: a LOINC code's display and properties. glucose = client.code_system_lookup(system="http://loinc.org", code="2345-7") # Questionnaires: a form by its id, such as the PHQ-9. phq9 = client.read_questionnaire("44249-1") # Plan Benefits: a Medicare Advantage or ACA Marketplace plan, and which providers are in its network. plan = client.read_insurance_plan("H2406-013-000") network = client.check_insurance_plan_network("H3359-038-000", npi=["1518929058"]) # MedList: one reconciled medication list from one patient's FHIR medication records. It receives # patient data, so send it only under an executed agreement. reconciled = client.reconcile_medications(records) ``` ```go Go theme={null} // Scheduling: open appointment slots, here in Boston. found, err := client.SearchSlots(ctx, haau3.SearchSlotsParams{City: new("Boston"), State: new("MA"), Limit: new(10)}) // Provider directory: a practitioner by NPI, from CMS's National Provider Directory. practitioners, err := client.SearchPractitioner(ctx, haau3.SearchPractitionerParams{Identifier: new("1093209884")}) // Terminology: a LOINC code's display and properties. glucose, err := client.CodeSystemLookup(ctx, haau3.CodeSystemLookupParams{System: new("http://loinc.org"), Code: "2345-7"}) // Questionnaires: a form by its id, such as the PHQ-9. phq9, err := client.ReadQuestionnaire(ctx, "44249-1") // Plan Benefits: a Medicare Advantage or ACA Marketplace plan, and which providers are in its network. plan, err := client.ReadInsurancePlan(ctx, "H2406-013-000") network, err := client.CheckInsurancePlanNetwork(ctx, "H3359-038-000", haau3.CheckInsurancePlanNetworkParams{NPI: []string{"1518929058"}}) // MedList: one reconciled medication list from one patient's FHIR medication records. It receives // patient data, so send it only under an executed agreement. reconciled, err := client.ReconcileMedications(ctx, records) ``` Each SDK's `api.md` lists every method by service, with its request and its page in the API reference: [TypeScript](https://github.com/haau3/haau3-node/blob/main/api.md), [Python](https://github.com/haau3/haau3-python/blob/main/api.md) and [Go](https://github.com/haau3/haau3-go/blob/main/api.md). ## Errors ```ts TypeScript theme={null} import { AuthError, NoAnswerError, RefusedError } from "@haau3/sdk"; try { await haau3.readInsurancePlan(id); } catch (error) { if (error instanceof RefusedError) { error.status; // 400, 404, 410 and so on error.outcome; // the OperationOutcome; Scheduling's errors carry `problem` } else if (error instanceof NoAnswerError) { error.reason; // "timeout", "network", "rate-limited", "unavailable", "server-error" or "unreadable" error.retryAfter; // seconds, when the API said error.detail; // for a timeout or a network failure: what failed, such as "ECONNREFUSED" or "TimeoutError" } else if (error instanceof AuthError) { // the key was missing, unknown or revoked } } ``` ```python Python theme={null} from haau3 import AuthError, NoAnswerError, RefusedError try: client.read_insurance_plan(plan_id) except RefusedError as error: print(error.status) # 400, 404, 410 and so on print(error.outcome) # the OperationOutcome; Scheduling's errors carry `problem` except NoAnswerError as error: print(error.reason) # "timeout", "network", "rate-limited", "unavailable", "server-error" or "unreadable" print(error.retry_after) # seconds, when the API said print(error.detail) # for a timeout or a network failure: what failed, such as "ConnectError: Connection refused" except AuthError: pass # the key was missing, unknown or revoked ``` ```go Go theme={null} plan, err := client.ReadInsurancePlan(ctx, planID) var e *haau3.Error if errors.As(err, &e) { switch e.Kind { case haau3.KindRefused: fmt.Println(e.Status) // 400, 404, 410 and so on fmt.Println(e.Outcome()) // the OperationOutcome; Scheduling's errors carry e.Problem() case haau3.KindNoAnswer: fmt.Println(e.Reason) // timeout, network, rate-limited, unavailable, server-error or unreadable fmt.Println(e.RetryAfter) // when the API said fmt.Println(e.Detail) // for a timeout or a network failure: what failed, such as "dial: connection refused" case haau3.KindAuth: // the key was missing, unknown or revoked case haau3.KindNotSent: // the SDK refused to send it: a required parameter left empty, a URL on another host } } ``` Every error the API answered carries its request id. No answer is never reported as an empty result, and an empty result is never an error. An error's message never includes the body the API answered with, and the body is never printed when the error is logged. A FHIR answer is checked against the resource types its operation answers with, and anything else is a no-answer error with the reason `unreadable`. [Errors](/errors) lists every status the API answers. ## Request ids and timing ```ts TypeScript theme={null} const { data: plan, requestId, durationMs, attempts } = await haau3.readInsurancePlan(id).withResponse(); ``` ```python Python theme={null} response = client.with_response.read_insurance_plan(plan_id) print(response.data.get("name"), response.request_id, response.duration_ms, response.attempts) ``` ```go Go theme={null} var info haau3.ResponseInfo plan, err := client.ReadInsurancePlan(ctx, planID, haau3.WithResponseInfo(&info)) fmt.Println(info.RequestID, info.Duration, info.Attempts) ``` Quote the request id when you ask for help: it finds the request in the API's own logs. To find a request by an id of your own, pass a UUID as the call's correlation id, and it comes back on the answer and on every error the API answered. ## Paging Each SDK follows a FHIR search's `next` links on the client's own host, one page at a time, or one resource at a time across every page: ```ts TypeScript theme={null} const first = await haau3.searchInsurancePlan({ "coverage-area-state": "NC" }); for await (const page of haau3.pages(first)) { for (const entry of page.entry ?? []) console.log(entry.resource?.id); } ``` ```python Python theme={null} first = client.search_insurance_plan(coverage_area_state="NC") for page in client.pages(first): for entry in page.get("entry", []): resource = entry.get("resource") if resource is not None: print(resource.get("id")) ``` ```go Go theme={null} first, err := client.SearchInsurancePlan(ctx, haau3.SearchInsurancePlanParams{CoverageAreaState: new("NC")}) if err != nil { return err } for page, err := range client.Pages(ctx, first) { if err != nil { return err } for _, entry := range page.Entry { if plan, ok := entry.Resource.(*fhir.InsurancePlan); ok { fmt.Println(*plan.ID) } } } ``` ```ts TypeScript theme={null} for await (const resource of haau3.resources(first)) { if (resource.resourceType === "InsurancePlan") console.log(resource.name); } ``` ```python Python theme={null} for resource in client.resources(first): if resource["resourceType"] == "InsurancePlan": print(resource.get("name")) ``` ```go Go theme={null} for resource, err := range client.Resources(ctx, first) { if err != nil { return err } fmt.Println(resource.ResourceType()) } ``` Scheduling's searches page with `limit` and `offset` instead, and answer with a `total`. ## Several requests in one A batch sends several operations of the FHIR base as one request, and each gets its own result, in the order given: ```ts TypeScript theme={null} const [plan, counties] = await haau3.batch((b) => [ b.readInsurancePlan("H2406-013-000"), b.listInsurancePlanCounties({ state: "NC" }), ]); if (plan.ok) { plan.data; // fhir4.InsurancePlan } else { plan.error; // a RefusedError or a NoAnswerError, as readInsurancePlan alone would have thrown } ``` ```python Python theme={null} from haau3 import BatchOk, calls plan_result, counties_result = client.batch( calls.read_insurance_plan("H2406-013-000"), calls.list_insurance_plan_counties(state="NC"), ) if isinstance(plan_result, BatchOk): print(plan_result.data.get("name")) # an InsurancePlan else: print(plan_result.error) # a RefusedError or a NoAnswerError, as read_insurance_plan alone would have raised ``` ```go Go theme={null} planCall := haau3.Calls.ReadInsurancePlan("H2406-013-000") counties := haau3.Calls.ListInsurancePlanCounties(haau3.ListInsurancePlanCountiesParams{State: "NC"}) if err := client.Batch(ctx, []haau3.Entry{planCall, counties}); err != nil { return err // the batch as a whole failed } p, err := planCall.Result() // an *fhir.InsurancePlan, or the error ReadInsurancePlan alone would have returned ``` An entry that fails does so alone, while the others answer. The API takes up to 100 entries in up to 1 MiB, and gives a batch 60 seconds. Every operation on the FHIR base can go in a batch except the batch itself and MedList's `$reconcile-medications`. ## Any request Each SDK sends a request as you give it and returns what the API answered, whatever the status: ```ts TypeScript theme={null} const { status, statusText, body, requestId } = await haau3.request({ method: "GET", path: "/v1/scheduling/publishers" }); ``` ```python Python theme={null} raw = client.request("GET", "/v1/scheduling/publishers") print(raw.status, raw.reason_phrase, raw.request_id) ``` ```go Go theme={null} raw, err := client.Request(ctx, haau3.RawRequest{Method: "GET", Path: "/v1/scheduling/publishers"}) if err != nil { return err } fmt.Println(raw.Status, raw.StatusText, raw.RequestID, len(raw.Body())) ``` ## Options Every option has a default: | | TypeScript | Python | Go | Default | | - | - | - | - | - | | API key | `apiKey` | `api_key` | `WithAPIKey` | `HAAU3_API_KEY` | | Where the API is | `baseURL` | `base_url` | `WithBaseURL` | `HAAU3_BASE_URL`, then `https://api.haau3.com` | | Time for one attempt | `timeout` | `timeout` | `WithTimeout` | 65 seconds | | Retries | `maxRetries` | `max_retries` | `WithMaxRetries` | 1 | A call can set its own time and retries too. A retry waits a short, growing interval, or what the API's `Retry-After` header says when that is 60 seconds or less. A longer one fails at once, carrying it. The API key is sent only to the base URL's host. Set `HAAU3_BASE_URL` to run code you have not changed against a stand-in API of your own. ## Frameworks and testing Each README covers its own language's frameworks and tests: * TypeScript: [any runtime's `fetch` options](https://github.com/haau3/haau3-node#options), such as a proxy in Node or Next.js's caching. * Python: [FastAPI, Django and Flask](https://github.com/haau3/haau3-python#using-it-with-fastapi-django-and-flask), and [testing your code](https://github.com/haau3/haau3-python#testing-your-code). * Go: [net/http and Gin](https://github.com/haau3/haau3-go#using-it-with-nethttp-and-gin), and [testing your code](https://github.com/haau3/haau3-go#testing-your-code). ## Versions Each SDK is `0.y.z` while the API is in preview: a minor release may change what a method takes or answers, and a patch release never does. Each repository's `CHANGELOG.md` lists every release, and a release that takes a new version of the API's spec names that version. # Terminology service Source: https://docs.haau3.com/terminology/terminology-service A FHIR R4 terminology service: look up codes, expand value sets, and validate codes against published value sets. The **haau3 Terminology API** is a hosted **FHIR R4 terminology service**. It serves standard FHIR terminology operations over clinical code systems, so you can look up codes, expand value sets, and validate codes without running a terminology server yourself. ## Service base ``` https://api.haau3.com/v1/fhir/r4 ``` 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: ``` Content-Type: application/fhir+json; fhirVersion=4.0 ``` 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](/errors) 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 [Provider directory](/provider-directory/provider-directory), [Questionnaires](/questionnaires/questionnaires), [Plan Benefits](/plan-benefits/plan-benefits) and [MedList](/medlist/medlist) are served from this same base, under the same `CapabilityStatement`. ## Operations Each requires a valid API key (see [Authentication](/authentication)). Each accepts **GET** with query parameters or **POST** with a FHIR `Parameters` resource. * `CodeSystem/$lookup` — a code's display and properties. * `CodeSystem/$validate-code` — is this a real code in this code system? * `ValueSet/$expand` — expand a value set to its member codes. * `ValueSet/$validate-code` — is this code a member of this value set? * `metadata` — the `CapabilityStatement`, or a `TerminologyCapabilities` with `?mode=terminology`. See the [API Reference](/api-reference) for the full parameter list. A parameter an operation does not take, or one sent twice, is refused with a `400` that names it; `property` on `$lookup` is the one that repeats. See [parameters](/errors#parameters). ## Look up a code ```bash GET theme={null} curl "https://api.haau3.com/v1/fhir/r4/CodeSystem/\$lookup?system=http://loinc.org&code=2345-7&property=SHORTNAME" \ -H "Authorization: Bearer $HAAU3_API_KEY" ``` ```bash POST theme={null} curl -X POST "https://api.haau3.com/v1/fhir/r4/CodeSystem/\$lookup" \ -H "Authorization: Bearer $HAAU3_API_KEY" \ -H "Content-Type: application/fhir+json" \ -d '{ "resourceType": "Parameters", "parameter": [ { "name": "system", "valueUri": "http://loinc.org" }, { "name": "code", "valueCode": "2345-7" }, { "name": "property", "valueCode": "SHORTNAME" } ] }' ``` `property` is repeatable, up to 100 times, and narrows what comes back. Leave it off and you get everything the code system knows about the code. On `$lookup`, `displayLanguage` defaults to `en-US`, which keeps responses small. The upstream code system otherwise returns around thirty translations per code, which is roughly fourteen times the payload for content most callers discard. `$expand` has no such default: it sends `displayLanguage` only when you supply one. ## Three ways to pass a code The validate operations accept a code in three shapes. The first works everywhere; the other two are complex FHIR datatypes, which the operations framework only permits in a POST body — sent in a query string they return a `400` explaining exactly that. **1. `system` + `code` — two separate parameters, GET or POST.** The form used in every example above. **2. `coding` — the two bundled as one object, POST only.** In a real FHIR record a code does not live as two loose fields; it lives as a `Coding`. This form lets you pass it without taking it apart first: ```bash theme={null} curl -X POST "https://api.haau3.com/v1/fhir/r4/ValueSet/\$validate-code" \ -H "Authorization: Bearer $HAAU3_API_KEY" \ -H "Content-Type: application/fhir+json" \ -d '{ "resourceType": "Parameters", "parameter": [ { "name": "url", "valueUri": "http://hl7.org/fhir/ValueSet/observation-vitalsignresult" }, { "name": "coding", "valueCoding": { "system": "http://loinc.org", "code": "8867-4" } } ] }' ``` On `CodeSystem/$validate-code` the coding's `system` stands in for `url` — the code system is the thing being asked about. **3. `codeableConcept` — one concept, several labels, POST only.** A `CodeableConcept` is a single idea coded more than one way, and it validates as a member if **any** of its codings is. This is the shape real clinical data actually has: exports routinely carry a vendor-local coding alongside a standard one on the same element. The vendor label cannot decide membership — no terminology server has ever heard of it — but the standard label beside it can, and this form means you never have to guess which label to send: ```bash theme={null} curl -X POST "https://api.haau3.com/v1/fhir/r4/ValueSet/\$validate-code" \ -H "Authorization: Bearer $HAAU3_API_KEY" \ -H "Content-Type: application/fhir+json" \ -d '{ "resourceType": "Parameters", "parameter": [ { "name": "url", "valueUri": "http://hl7.org/fhir/us/core/ValueSet/us-core-laboratory-test-codes" }, { "name": "codeableConcept", "valueCodeableConcept": { "text": "GLUCOSE, SERUM", "coding": [ { "system": "urn:oid:1.2.3.4.5.999", "code": "GLU-123" }, { "system": "http://loinc.org", "code": "2345-7" } ] } } ] }' ``` ```json theme={null} { "resourceType": "Parameters", "parameter": [ { "name": "result", "valueBoolean": true }, { "name": "message", "valueString": "Matched on coding http://loinc.org|2345-7." } ] } ``` When no coding matches, the `message` names each label's own reason — an unrecognised vendor system and a real code that simply is not in the set are different kinds of no. And if the request could not actually be answered — the value set does not exist, a shared parameter is invalid, a code system was unreachable — the whole request answers with that error rather than a false. "We could not ask" is never reported as "no", and the concept form always agrees with what the plain `system` + `code` form would say. Two details of how codings are read: * **Per-coding membership ignores each coding's `display`.** A display mismatch fails validation even when the code is a member, and a vendor-worded label must not veto a right code. Display checking is available on the single-`coding` form, where the response tells you the code was valid and only the label differed. * **`coding.version` is honoured on the CodeSystem operations and refused on ValueSet validation** (which always uses the current code-system version) rather than being silently ignored. Value sets and code systems are addressed by **canonical URL only** (the `url` parameter). Instance-level operation paths (`/ValueSet/{id}/$validate-code`) are not offered: this service hosts published definitions whose canonical URLs are their identity, so there are no server-assigned ids to address. ## Validating against a hosted value set Some published value sets are defined as a **rule** rather than a list — "every code in this system with such-and-such property" — and resolve to tens of thousands of members. This service hosts a number of those definitions and answers membership for them directly, so you can ask about a code without expanding anything: ```bash theme={null} curl -G "https://api.haau3.com/v1/fhir/r4/ValueSet/\$validate-code" \ -H "Authorization: Bearer $HAAU3_API_KEY" \ --data-urlencode "url=" \ --data-urlencode "system=http://loinc.org" \ --data-urlencode "code=" ``` ```json theme={null} { "resourceType": "Parameters", "parameter": [{ "name": "result", "valueBoolean": true }] } ``` `GET /v1/fhir/r4/metadata?mode=terminology` lists the value sets and code systems this service answers for. A canonical it does not host and cannot resolve upstream is refused with an `OperationOutcome`, never answered with a guess. `$expand` is refused for rule-defined value sets whose expansion runs to many thousands of codes, and for the whole of LOINC. A truncated expansion looks complete, which is worse than a clear refusal, and `$validate-code` answers membership without materialising the set. ## Expanding a value set An expansion comes back whole. `url` is the only parameter accepted for a value set resolved from its own source: `filter`, `count` and `offset` are refused there with a `400` naming them, because that source answers a whole expansion or nothing. Expand the set and filter the result. On value sets this service hosts, those three narrow the expansion normally. `displayLanguage`, `includeDesignations` and `activeOnly` are refused on `$expand` everywhere, also with a `400` naming them. An expansion is returned in its source's own language and order. The whole of LOINC (`http://loinc.org?fhir_vs`, the value set `url` defaults to when it is left out) is not expanded: it answers `422`. To ask whether a code is in LOINC, use `$validate-code`, which answers it against the whole of LOINC. To list codes, expand a LOINC group or answer list, for example `http://loinc.org/vs/LG51070-7`. ```bash theme={null} curl -G "https://api.haau3.com/v1/fhir/r4/ValueSet/\$expand" \ -H "Authorization: Bearer $HAAU3_API_KEY" \ --data-urlencode "url=" ``` **An empty expansion does not always mean the value set is empty.** For a canonical inside a source's own namespace, a set that does not exist can come back as a successful expansion with no members rather than as an error, so an empty result cannot be read as proof that the set exists and is empty. Use `$validate-code` when you need a decided answer about a specific code, and treat `total: 0` as "nothing to show" rather than as a fact about the set. ## Several operations in one request POST a FHIR `Bundle` with `type: batch` to the service base. Entries are independent: one unknown code does not fail the others, and each carries its own status. ```bash theme={null} curl -X POST "https://api.haau3.com/v1/fhir/r4" \ -H "Authorization: Bearer $HAAU3_API_KEY" \ -H "Content-Type: application/fhir+json" \ -d '{ "resourceType": "Bundle", "type": "batch", "entry": [ { "request": { "method": "GET", "url": "CodeSystem/$lookup?system=http://loinc.org&code=2345-7" } }, { "request": { "method": "GET", "url": "CodeSystem/$lookup?system=http://loinc.org&code=8867-4" } } ] }' ``` This is worth using for anything more than a handful of codes. Measured against the upstream, a batch of fifty lookups costs about 5 ms per code, against about 180 ms each when sent one at a time. Transaction bundles are not accepted: these operations do not write anything, so all-or-nothing would only turn one bad code into a wholly failed request. A batch carries at most 100 entries in at most 1 MiB, and has 60 seconds: an entry still running then answers `504` inside the batch's `200`. A `Parameters` body sent to one operation has the same 1 MiB limit. See [limits](/errors#limits). ## Responses and errors Success returns standard FHIR: a `Parameters` for `$lookup` and `$validate-code`, a `ValueSet` for `$expand`. **Every error is a FHIR `OperationOutcome`**, with `issue.code` telling you what kind of problem it is. | Status | Meaning | | - | - | | `400` | Invalid parameters, or two different FHIR versions in one request. | | `404` | The code system is not served here, or the code was not found. | | `422` | The request is well formed but cannot be answered as asked. | | `502` / `504` | An upstream code system could not be reached, refused or failed the request, or did not answer in time. | | `503` | No source is configured for that code system on this deployment, or its source is rate limiting requests. A rate-limited answer carries `Retry-After`. | The statuses every service shares, among them `401`, `405`, `406`, `415`, `429` and `500`, are on [Errors, limits and request ids](/errors), which also says when to send a `502`, `503` or `504` again, including inside a batch. Two things worth knowing: * **A code that is not in a value set is a successful `200`** with `"result": false`, not an error. * **`result: false` can also mean the display you sent does not match.** If you pass `display`, it is checked against the code's real display, and a mismatch returns `false` *with the expected display alongside it*. The code is fine; only the label differs. Vendor labels differ from published displays routinely, so do not read that as an invalid code. We never pass an upstream error through unchanged. Upstream terminology servers are not consistently valid FHIR and their messages name their own internals, so errors are translated into outcomes of our own, with the upstream text kept as supporting `diagnostics`. Code systems we hold ourselves have no upstream to pass through; their errors are written here. ## Code systems | Code system | URI | Status | Notes | | - | - | - | - | | LOINC | `http://loinc.org` | Served | Available to any caller with an API key. | | SNOMED CT | `http://snomed.info/sct` | Not served | SNOMED CT is free in the United States only through a UMLS Metathesaurus licence, and NLM does not permit a vendor to cover its customers under its own licence. Callers holding their own UMLS credentials will be able to use them here; there is no path that serves SNOMED CT to an unlicensed caller. | | CPT | `http://www.ama-assn.org/go/cpt` | Not served | CPT is licensed by the American Medical Association and requires a paid distributor licence per product. haau3 does not hold one, so this service will not answer about CPT codes at all. | | ICD-10-CM | `http://hl7.org/fhir/sid/icd-10-cm` | Not served | ICD-10-CM is public domain and carries no licence restriction, but this service does not yet have a source configured for it. | | RxNorm | `http://www.nlm.nih.gov/research/umls/rxnorm` | Served | Available to any caller with an API key. Held locally at a named release: `GET /v1/fhir/r4/metadata?mode=terminology` names the release being served, and answers 503 on a deployment that has no release published yet. | | UCUM | `http://unitsofmeasure.org` | Not served | UCUM is a grammar rather than a list of codes, so it is validated by a unit parser rather than looked up on a terminology server. A lookup endpoint is the wrong shape for the question. | A request naming a system that is not served comes back as a FHIR `OperationOutcome` with an issue code of `not-supported`, saying which kind of no it is: a system nobody publishes, a system we are not licensed to serve, or one that needs a licence you hold rather than one we can pass on. The service does not guess. Systems that are not listed at all are also answered rather than errored. Most real-world coded data carries site-specific systems that no terminology server can resolve, so an honest "I do not recognise this" is a normal answer here, not a failure. This table is the service's intent, and it is generated from the registry so it cannot drift from the code. What a particular deployment is answering right now — including which release it holds of a code system it keeps locally — comes from `GET /v1/fhir/r4/metadata?mode=terminology`. ## RxNorm Most code systems here are resolved from their own source when you ask. RxNorm is different: we hold it, at a named release, and answer from our own copy. That changes two things you can see. Answers are fast and do not depend on anyone else being up. And every answer tells you which release it came from, so a result you got last month and a result you get today are comparable. **Which release you are talking to** is in `TerminologyCapabilities`: ```bash theme={null} curl "https://api.haau3.com/v1/fhir/r4/metadata?mode=terminology" \ -H "Authorization: Bearer $HAAU3_API_KEY" ``` RxNorm appears under `codeSystem` with a `version`, and the same value comes back on every `$lookup`. If a deployment has no release published yet, RxNorm is listed as not served with the reason, and requests answer `503` rather than pretending. ### Looking up a concept ```bash theme={null} curl "https://api.haau3.com/v1/fhir/r4/CodeSystem/\$lookup?system=http://www.nlm.nih.gov/research/umls/rxnorm&code=" \ -H "Authorization: Bearer $HAAU3_API_KEY" ``` The `system` and `url` parameters default to LOINC, so an RxNorm request has to name RxNorm explicitly. Leave it off and you are asking a different question. You get the concept's name, its other names as `designation` entries, and a `property` for each of: * **RxNorm's own attributes**, under RxNorm's own names — term type, strength, quantity, dispensable-form flags, NDC codes and the rest. We do not rename them, so what you read here matches what you read in RxNorm's documentation. * **`inactive`**, FHIR's standard property, which is `false` for everything we hold. The content set we hold contains active concepts only. RxNorm's own suppressibility flag comes back separately under its own name, because one of its values means "unquantified but current", and reporting such a concept as inactive would tell you it had been retired. * **each relationship the concept has**, keyed by RxNorm's name for it and carrying the related concept as a `Coding`. So a brand drug's relationship to its clinical drug comes back with the other concept's code and display, not just a label you would have to look up again. Narrow what comes back with `property=` — repeatable up to 100 times, and a name we have no value for is simply absent rather than an error. ### What we hold, and what that means for a "no" We hold **RxNorm Current Prescribable Content**, the subset NLM publishes for currently prescribable drugs. As NLM defines it, that is active normalized names, and it excludes obsolete or suppressed data, drugs that are exclusively non-US, and drugs for exclusive veterinary use. A code we do not hold comes back as `404`, not as a negative result. We hold a subset, so we cannot tell a code that never existed from one that exists outside the subset — and answering "not valid" for the second would be a guess. The outcome says so. This is also why membership questions use our own value set for the content we hold rather than a canonical meaning all of RxNorm: ```bash theme={null} curl -X POST "https://api.haau3.com/v1/fhir/r4/ValueSet/\$validate-code" \ -H "Authorization: Bearer $HAAU3_API_KEY" \ -H "Content-Type: application/fhir+json" \ -d '{ "resourceType": "Parameters", "parameter": [ { "name": "url", "valueUri": "https://haau3.com/fhir/ValueSet/rxnorm-current-prescribable-content" }, { "name": "coding", "valueCoding": { "system": "http://www.nlm.nih.gov/research/umls/rxnorm", "code": "" } } ] }' ``` Membership of *that* set is something we know, so `result` is a real answer either way. ### Asking for a particular release `version` is honoured when it names the release we are serving, in either the FHIR form (the release date as it appears in the source's file names) or the source's own form. Any other value is refused by name: we hold one release at a time, and quietly answering about a different one would answer a question you did not ask. ### Not offered `ValueSet/$expand` of the content set. It runs to tens of thousands of concepts, and an expansion we truncated would read as a complete list. Ask about a code with `ValueSet/$validate-code`, or look it up. ## Legal This material contains content from LOINC ([http://loinc.org](http://loinc.org)). LOINC is copyright © Regenstrief Institute, Inc. and the Logical Observation Identifiers Names and Codes (LOINC) Committee and is available at no cost under the license at [http://loinc.org/license](http://loinc.org/license). LOINC® is a registered United States trademark of Regenstrief Institute, Inc. Some individual codes carry a further copyright held by whoever created the underlying instrument, most often a survey or assessment. Where one does, it comes back with the code as an `EXTERNAL_COPYRIGHT_NOTICE` property, and you must keep it with the code wherever you use it. This product uses publicly available data courtesy of the U.S. National Library of Medicine (NLM), National Institutes of Health, Department of Health and Human Services; NLM is not responsible for the product and does not endorse or recommend this or any other product. Code systems we hold locally are served from a dated release, named in the `version` of every answer and in `TerminologyCapabilities`. A held release may not reflect the most current data available from its publisher. FHIR® is the registered trademark of Health Level Seven International and its use here does not constitute endorsement by HL7. Full terms are at [platform.haau3.com/terms](https://platform.haau3.com/terms). For your first call, start with the [Quickstart](/quickstart).