openapi: 3.1.0 info: title: OpenDoc Platform API version: "1.1.0" description: | # OpenDoc Platform API Healthcare transaction infrastructure. Resolve intent to bounded, pre-priced healthcare transactions. Create, authorize, and settle Health Keys. Query the HSO registry. Route clinical intent to providers. Manage funding and settlement. ## Current Status: Live Today vs Opening With First Partners The engine is live and the sandbox is open to everyone: `POST /developers/signup` on the live API returns an `odk_sandbox_` key instantly. Sandbox keys drive the full booking and payment state machine against a synthetic provider with simulated (non-live) payments — no real money, no real PHI. Live mode — real provider inventory and real-money transactions — opens with our first design partners and is granted per-partner. This document is the full API contract, including capabilities that are ahead of the current deployment. The authoritative live capability map is `GET https://api.opendoc.com/protocol` (see `agent_self_serve` for the current sandbox/live posture). Nothing in this spec should be read as implying you can transact against live provider inventory today. ## Foundational Invariant: Cash-Only Transactions OpenDoc operates exclusively on cash-price transactions. Insurance cannot exist inside the transaction loop. This is not a preference — it is a mathematical necessity. The Cash Price Invariant proves by contradiction that price guarantees are impossible when a third-party adjudicator with post-visit repricing authority is in the settlement path. Every price on this platform is a cash price. Every price is final at acceptance. Every price is HIGH confidence. There are no estimates, no "allowed amounts," no deductible calculations, and no EOB dependencies. Platform partners who wish to help patients compare their cash-price options against insurance alternatives may do so in their own UIs using external data sources. OpenDoc does not perform that comparison. ## Core Concepts - **HSO (Health Service Object)**: Atomic, pre-care contract binding service definition, provider obligation, price, and completion semantics. - **mHSO (Membership HSO)**: Time-bounded, price-deterministic contract for longitudinal care access and coordination. - **Health Key**: Patient-controlled transaction credential. States: READY, ACTIVE, DECISION_REQUIRED, NOT_AUTHORIZED, COMPLETED, SETTLED, CANCELLED. - **Offer**: A provider × HSO × location binding with a specific cash price. - **Funding**: Upfront capital held from any source (patient, individual, charity, employer, BNPL). All sources are additive, held independently, and settled on service completion. (Funds are held as a delayed payout of the provider's collected funds — agent of the payee — not neutral escrow.) ## Authentication All requests require a Bearer token issued during client registration. Patient-context endpoints require a patient session token obtained via OAuth 2.0 authorization code flow with PKCE. Provider-context endpoints require a provider session token or platform delegation token with provider authorization. ## Standard Response Envelope Every response includes the following top-level fields: - `request_id` (UUID) — unique per request - `api_version` — e.g. "1.0" - `schema_version` — e.g. "1.0" - `timestamp` — ISO 8601 These are omitted from individual schema definitions for brevity but are always present in the wire format. ## Idempotency All state-mutating operations (POST, PATCH, DELETE) accept an `Idempotency-Key` header (UUID). Duplicate requests with the same key return the original response without re-executing the operation. ## Rate Limits Limits are per-client and per-patient. Tier 2/3 consent data access has daily budgets to prevent slow exfiltration. Rate limit headers are returned on every response: `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `Retry-After`. ## Banned Terminology Per the Anti-PRD, the following terms never appear in API responses: "balance due", "final bill", "insurance price", "covered service", "approved", "denied", "HSA-eligible", "you owe", "pending adjudication". contact: name: OpenDoc Platform Team email: platform@opendoc.com license: name: Proprietary url: https://opendoc.com/terms servers: - url: https://api.opendoc.com/v1 description: Production (contract base URL — the current deployment serves routes from https://api.opendoc.com; confirm with GET /protocol) - url: https://sandbox.api.opendoc.com/v1 description: Sandbox (planned dedicated host — today the sandbox runs on the production host via odk_sandbox_ keys; synthetic provider, simulated payments, no real transactions) security: - BearerAuth: [] tags: - name: Taxonomy description: | Canonical clinical and service taxonomies. Query symptoms, diagnoses, HSO scope vocabulary, and specialty classifications. These are the lookup tables that power resolve_to_care and membership scope definitions. - name: HSO Registry description: | The canonical catalog of Health Service Objects. Read-only for platform partners. HSOs are maintained by OpenDoc and versioned centrally. - name: Providers description: | Provider directory. Read access for platform partners. Write access for providers and platforms with provider delegation. - name: Offers description: | An Offer is the binding of Provider × HSO × Location × Cash Price. Write access for providers. Read access for all authenticated clients. - name: Resolve to Care description: | The Clinical Routing Engine (CRE). Converts patient intent into ranked HSO candidates, provider candidates, and cash pricing. Deterministic: same inputs + same policy version = same outputs. - name: Patients description: | Patient identity creation and management. Patients are created via OAuth-linked registration or platform-initiated enrollment. - name: Health Keys description: | The Health Key lifecycle — creation, authorization, state management, QR payload, scan verification. See State Transitions section. - name: Memberships description: | Membership HSO (mHSO) lifecycle. Provider-side tier management and patient-side enrollment, renewal, and cancellation. - name: Funding description: | Multi-source funding orchestration. All sources held independently, settled on service completion. See Funding State Machine. - name: Settlement description: | Event-based settlement. Funds move exactly once on Proof of Service. Disputes are downstream only, time-bounded, and reason-enumerated. - name: Consent description: | Tiered patient consent. Tier 1 default. Tier 2/3 require explicit opt-in. Revocation is immediate. - name: Audit description: | Patient-visible access logs and platform observability. - name: Webhooks description: | Event notifications to platform partners with HMAC signature verification. - name: Platform description: | Client registration, API key management, usage dashboards. # ═══════════════════════════════════════════════════════════════ # PATHS # ═══════════════════════════════════════════════════════════════ paths: # ─── TAXONOMY ─────────────────────────────────────────────── /taxonomy/complaints: get: operationId: listComplaints tags: [Taxonomy] summary: Search the symptom/complaint catalog description: | Query the canonical complaint taxonomy. Each complaint has a stable ID (e.g. `complaint.knee_pain`), consumer-facing synonyms, and mapped HSO candidates. This is the lookup table for `resolve_to_care` intent_type=COMPLAINT. parameters: - name: q in: query description: Free-text search against names and consumer synonyms schema: type: string example: "my knee hurts" - name: specialty in: query schema: type: string - $ref: '#/components/parameters/PageCursor' - $ref: '#/components/parameters/PageLimit' responses: '200': description: Complaint list content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/Complaint' pagination: $ref: '#/components/schemas/Pagination' /taxonomy/complaints/{complaint_id}: get: operationId: getComplaint tags: [Taxonomy] summary: Get a specific complaint with mapped HSOs parameters: - name: complaint_id in: path required: true schema: type: string example: "complaint.knee_pain" responses: '200': description: Complaint detail content: application/json: schema: $ref: '#/components/schemas/Complaint' /taxonomy/diagnoses: get: operationId: listDiagnoses tags: [Taxonomy] summary: Search the diagnosis catalog description: | Query the canonical diagnosis taxonomy. Used for `resolve_to_care` intent_type=DIAGNOSIS. parameters: - name: q in: query schema: type: string - name: specialty in: query schema: type: string - $ref: '#/components/parameters/PageCursor' - $ref: '#/components/parameters/PageLimit' responses: '200': description: Diagnosis list content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/Diagnosis' pagination: $ref: '#/components/schemas/Pagination' /taxonomy/diagnoses/{diagnosis_id}: get: operationId: getDiagnosis tags: [Taxonomy] summary: Get a specific diagnosis with mapped HSOs parameters: - name: diagnosis_id in: path required: true schema: type: string responses: '200': description: Diagnosis detail content: application/json: schema: $ref: '#/components/schemas/Diagnosis' /taxonomy/scope-codes: get: operationId: listScopeCodes tags: [Taxonomy] summary: List controlled vocabulary for mHSO scope description: | Returns the full controlled vocabulary of inclusion and exclusion codes used in membership tier scope definitions. Required for building tier creation UIs. parameters: - name: type in: query description: Filter to inclusion or exclusion codes schema: type: string enum: [INCLUSION, EXCLUSION, ALL] default: ALL - name: category in: query schema: type: string example: "visit" responses: '200': description: Scope code vocabulary content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/ScopeCode' required_exclusions: type: array items: type: string description: | Codes that MUST appear in every tier's excluded_scope. Tier creation validation fails without these. example: - "EXCL.HOSPITAL.*" - "EXCL.ANES.GENERAL" - "EXCL.IMAGING.ADVANCED" /taxonomy/specialties: get: operationId: listSpecialties tags: [Taxonomy] summary: List specialty and subspecialty classifications responses: '200': description: Specialty tree content: application/json: schema: type: object properties: data: type: array items: type: object properties: specialty: type: string subspecialties: type: array items: type: string hso_count: type: integer # ─── HSO REGISTRY ─────────────────────────────────────────── /hsos: get: operationId: listHSOs tags: [HSO Registry] summary: Search the HSO catalog parameters: - name: specialty in: query schema: type: string - name: subspecialty in: query schema: type: string - name: category in: query schema: type: string - name: q in: query description: Free-text search against names, aliases, and consumer synonyms schema: type: string - name: geo in: query description: Postal code — filter to HSOs with active offers nearby schema: type: string - name: radius_miles in: query schema: type: integer default: 25 minimum: 1 maximum: 100 - name: medicare_excluded in: query schema: type: boolean - name: membership_eligible in: query schema: type: boolean - $ref: '#/components/parameters/PageCursor' - $ref: '#/components/parameters/PageLimit' responses: '200': description: Paginated HSO list content: application/json: schema: $ref: '#/components/schemas/HSO_List' /hsos/{hso_id}: get: operationId: getHSO tags: [HSO Registry] summary: Get a single HSO description: | Returns the full canonical HSO specification. The `hso_id` is a semantic dot-notation identifier (e.g. `hso.ortho.knee.proc.total_replacement`) that encodes taxonomy position. It is immutable — it identifies the service definition, not a database row. parameters: - $ref: '#/components/parameters/HSO_ID' responses: '200': description: HSO detail content: application/json: schema: $ref: '#/components/schemas/HSO' '404': $ref: '#/components/responses/NotFound' /hsos/{hso_id}/offers: get: operationId: listHSOOffers tags: [HSO Registry] summary: List offers for this HSO parameters: - $ref: '#/components/parameters/HSO_ID' - name: geo in: query schema: type: string - name: radius_miles in: query schema: type: integer default: 25 - $ref: '#/components/parameters/PageCursor' - $ref: '#/components/parameters/PageLimit' responses: '200': description: Offers for this HSO content: application/json: schema: $ref: '#/components/schemas/Offer_List' # ─── PROVIDERS ────────────────────────────────────────────── /providers: get: operationId: listProviders tags: [Providers] summary: Search the provider directory parameters: - name: q in: query schema: type: string - name: specialty in: query schema: type: string - name: geo in: query schema: type: string - name: radius_miles in: query schema: type: integer default: 25 - name: medicare_opted_out in: query schema: type: boolean - name: accepting_new_patients in: query schema: type: boolean - $ref: '#/components/parameters/PageCursor' - $ref: '#/components/parameters/PageLimit' responses: '200': description: Provider list content: application/json: schema: $ref: '#/components/schemas/Provider_List' post: operationId: createProvider tags: [Providers] summary: Activate a provider on the platform description: | Registers a new provider entity. Requires NPI verification. Provider begins in PENDING state until verification completes. Can be called by the provider directly or by a platform partner with provider delegation authorization. security: - BearerAuth: [] - ProviderAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ProviderCreate' responses: '201': description: Provider created (PENDING verification) content: application/json: schema: $ref: '#/components/schemas/Provider' /providers/{provider_id}: get: operationId: getProvider tags: [Providers] summary: Get provider detail parameters: - $ref: '#/components/parameters/ProviderID' responses: '200': description: Provider detail content: application/json: schema: $ref: '#/components/schemas/Provider' '404': $ref: '#/components/responses/NotFound' patch: operationId: updateProvider tags: [Providers] summary: Update provider details description: | Update operational fields (accepting_new_patients, telehealth, locations). NPI and legal identity cannot be changed via PATCH. security: - ProviderAuth: [] parameters: - $ref: '#/components/parameters/ProviderID' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ProviderUpdate' responses: '200': description: Provider updated content: application/json: schema: $ref: '#/components/schemas/Provider' /providers/{provider_id}/offers: get: operationId: listProviderOffers tags: [Providers] summary: List all offers from a provider parameters: - $ref: '#/components/parameters/ProviderID' - name: location_id in: query schema: type: string format: uuid - $ref: '#/components/parameters/PageCursor' - $ref: '#/components/parameters/PageLimit' responses: '200': description: Provider's offers content: application/json: schema: $ref: '#/components/schemas/Offer_List' # ─── OFFERS ───────────────────────────────────────────────── /offers: get: operationId: listOffers tags: [Offers] summary: List offers (flat provider × HSO × SCP rate-card rows) description: | Public discovery list of all active offers across all providers, HSOs, and locations. Each row is one (provider, HSO, SCP) tuple with the current cash price. Used by the marketplace search results page; agents and partner integrations can hit it directly. Per ADR 0039 (offers list endpoint). parameters: - name: q in: query schema: type: string minLength: 1 maxLength: 200 description: | Free-text search across HSO title/description, provider name, and provider specialty. Case-insensitive substring match. - name: category in: query schema: type: string maxLength: 64 description: Filter by HSO category exact match. - name: specialty in: query schema: type: string maxLength: 64 description: Filter by provider primary specialty exact match. - name: limit in: query schema: type: integer default: 50 minimum: 1 maximum: 200 - name: offset in: query schema: type: integer default: 0 minimum: 0 responses: '200': description: Flat list of offers ordered by cash price ascending content: application/json: schema: $ref: '#/components/schemas/Offer_List' post: operationId: createOffer tags: [Offers] summary: Create an offer (bind provider to HSO at a price) description: | Creates an Offer: the binding of a provider to an HSO at a specific location with a specific cash price. The price is immutable once a Health Key references this offer. The price must be the provider's real, deliverable cash price. Per the pricing guide: "Does this price work even if insurance pays nothing? If yes, you are ready to list." security: - ProviderAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/OfferCreate' responses: '201': description: Offer created content: application/json: schema: $ref: '#/components/schemas/Offer' '400': description: | Validation failure. Common causes: provider not verified, HSO not in provider capabilities, location not registered. content: application/json: schema: $ref: '#/components/schemas/Error' /offers/{offer_id}: get: operationId: getOffer tags: [Offers] summary: Get offer detail parameters: - $ref: '#/components/parameters/OfferID' responses: '200': description: Offer detail content: application/json: schema: $ref: '#/components/schemas/Offer' patch: operationId: updateOffer tags: [Offers] summary: Update offer price or availability description: | Update the cash price or availability for an offer. Price changes apply prospectively only — any Health Key already referencing this offer retains the price at time of creation. **Cash Price Invariant**: A price accepted by a patient is final. This endpoint changes future offer pricing, never retroactive. security: - ProviderAuth: [] parameters: - $ref: '#/components/parameters/OfferID' requestBody: required: true content: application/json: schema: type: object properties: price_cents: type: integer minimum: 100 description: New cash price (applies to future Health Keys only) active: type: boolean description: Set false to pause this offer without deleting availability: $ref: '#/components/schemas/AvailabilitySummary' responses: '200': description: Offer updated content: application/json: schema: $ref: '#/components/schemas/Offer' delete: operationId: retireOffer tags: [Offers] summary: Retire an offer description: | Permanently retires an offer. Existing Health Keys referencing this offer are unaffected. No new Health Keys can be created against a retired offer. security: - ProviderAuth: [] parameters: - $ref: '#/components/parameters/OfferID' responses: '200': description: Offer retired content: application/json: schema: $ref: '#/components/schemas/Offer' # ─── RESOLVE TO CARE ──────────────────────────────────────── /resolve: post: operationId: resolveToCare tags: [Resolve to Care] summary: Resolve patient intent to bounded transaction candidates description: | The Clinical Routing Engine (CRE). Accepts a patient's clinical intent and returns a deterministic, ranked set of HSO candidates, provider candidates, and **cash pricing**. **Cash-only**: All prices returned are cash prices. There is no insurance context in the resolution pipeline. `pricing_confidence` is always HIGH for cash transactions. The `financial_context` field exists solely to help platform partners display comparative information in their own UIs (e.g. "your HSA balance can cover this") — it does not affect pricing, ranking, or resolution. **Determinism guarantee**: Same inputs + same `policy_version` = same ranked outputs. **Human confirmation constraint**: The response includes a `confirmation_url`. No third-party app may book, generate a binding credential, or trigger payment without explicit patient confirmation in the Health Key UI. requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ResolveRequest' responses: '200': description: Resolution result content: application/json: schema: $ref: '#/components/schemas/ResolveResponse' '400': $ref: '#/components/responses/BadRequest' '403': description: Consent required content: application/json: schema: $ref: '#/components/schemas/Error' example: code: CONSENT_REQUIRED message: "Tier 2 consent required for clinical context." details: required_tier: TIER2 consent_url: "https://app.opendoc.com/consent?grant=..." '429': $ref: '#/components/responses/RateLimited' # ─── PATIENTS ─────────────────────────────────────────────── /patients: post: operationId: createPatient tags: [Patients] summary: Register a patient description: | Creates a patient identity on the platform. Can be initiated by: 1. **Direct registration** — patient authenticates via OAuth and a patient record is created from verified identity claims. 2. **Platform-initiated** — a platform partner creates a patient record during their own onboarding flow, linked to the patient's identity via OAuth token exchange. The patient_id is the durable identifier used across all subsequent API calls. A patient must exist before a Health Key can be created. requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PatientCreate' responses: '201': description: Patient created content: application/json: schema: $ref: '#/components/schemas/Patient' '409': description: Patient already exists (matched on identity claims) content: application/json: schema: $ref: '#/components/schemas/Error' /patients/{patient_id}: get: operationId: getPatient tags: [Patients] summary: Get patient identity description: | Returns patient identity fields. Clinical data is NOT returned here — that requires consent-gated access via separate endpoints. parameters: - $ref: '#/components/parameters/PatientID' responses: '200': description: Patient identity content: application/json: schema: $ref: '#/components/schemas/Patient' # ─── HEALTH KEYS ──────────────────────────────────────────── /health-keys: get: operationId: listHealthKeys tags: [Health Keys] summary: List Health Keys description: | Query Health Keys by patient, provider, state, or date range. Platform partners see only Health Keys created through their integration. parameters: - name: patient_id in: query schema: type: string format: uuid - name: provider_id in: query schema: type: string format: uuid - name: state in: query schema: type: array items: $ref: '#/components/schemas/HealthKeyState' - name: created_after in: query schema: type: string format: date-time - name: created_before in: query schema: type: string format: date-time - $ref: '#/components/parameters/PageCursor' - $ref: '#/components/parameters/PageLimit' responses: '200': description: Health Key list content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/HealthKey' pagination: $ref: '#/components/schemas/Pagination' post: operationId: createHealthKey tags: [Health Keys] summary: Create a Health Key description: | Creates a Health Key in READY state binding a patient to an offer. The price is locked at creation from the offer's current cash price. **State: READY** — identity-only. Patient must authorize to transition to ACTIVE. See State Transitions table in HealthKeyState schema. requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/HealthKeyCreate' responses: '201': description: Health Key created (READY) content: application/json: schema: $ref: '#/components/schemas/HealthKey' '400': $ref: '#/components/responses/BadRequest' /health-keys/{health_key_id}: get: operationId: getHealthKey tags: [Health Keys] summary: Get Health Key detail parameters: - $ref: '#/components/parameters/HealthKeyID' responses: '200': description: Health Key detail content: application/json: schema: $ref: '#/components/schemas/HealthKey' /health-keys/{health_key_id}/authorize: post: operationId: authorizeHealthKey tags: [Health Keys] summary: Patient authorizes the Health Key description: | Transitions READY → ACTIVE. Patient confirms service, provider, and maximum financial responsibility. **Requires direct patient interaction.** The `confirmation_artifact` is a cryptographic proof of patient presence generated by the Health Key UI. Required in production; optional in sandbox. Valid transition: READY → ACTIVE parameters: - $ref: '#/components/parameters/HealthKeyID' requestBody: required: true content: application/json: schema: type: object required: [patient_confirmation, confirmation_artifact] properties: patient_confirmation: type: boolean enum: [true] confirmation_artifact: type: string description: Cryptographic proof of patient presence responses: '200': description: Health Key authorized (ACTIVE) content: application/json: schema: $ref: '#/components/schemas/HealthKey' '409': description: Invalid state transition (not in READY state) content: application/json: schema: $ref: '#/components/schemas/Error' /health-keys/{health_key_id}/complete: post: operationId: completeHealthKey tags: [Health Keys] summary: Attest service completion (Proof of Service) description: | Provider or system attests completion. Triggers immediate settlement. PoS tier determines dispute immunity. Valid transition: ACTIVE → COMPLETED (then auto → SETTLED after dispute window) Settlement is immediate. No batching, no delays, no discretionary holds. security: - ProviderAuth: [] parameters: - $ref: '#/components/parameters/HealthKeyID' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ProofOfService' responses: '200': description: Service completed, settlement initiated content: application/json: schema: $ref: '#/components/schemas/HealthKey' '409': description: Invalid state transition (not ACTIVE) content: application/json: schema: $ref: '#/components/schemas/Error' /health-keys/{health_key_id}/cancel: post: operationId: cancelHealthKey tags: [Health Keys] summary: Cancel the Health Key description: | Valid transitions: READY → CANCELLED, ACTIVE → CANCELLED. Cannot cancel COMPLETED, SETTLED, or already CANCELLED. Refund behavior depends on service_state at cancellation time. parameters: - $ref: '#/components/parameters/HealthKeyID' requestBody: content: application/json: schema: type: object required: [initiated_by] properties: reason: type: string enum: [PATIENT_REQUEST, PROVIDER_CANCELLATION, SYSTEM_ERROR] initiated_by: type: string enum: [PATIENT, PROVIDER, SYSTEM] responses: '200': description: Health Key cancelled content: application/json: schema: $ref: '#/components/schemas/HealthKey' '409': description: Cannot cancel in current state content: application/json: schema: $ref: '#/components/schemas/Error' /health-keys/{health_key_id}/qr: get: operationId: getHealthKeyQR tags: [Health Keys] summary: Get the QR authority token description: | Returns the QR payload for scanning. No PHI in plaintext. The signed token carries its own validity window for offline verification — scannable in clinic basements and OR environments. parameters: - $ref: '#/components/parameters/HealthKeyID' - name: format in: query schema: type: string enum: [json, png, svg] default: json responses: '200': description: QR payload or image content: application/json: schema: $ref: '#/components/schemas/QRPayload' image/png: schema: type: string format: binary image/svg+xml: schema: type: string /scan: post: operationId: scanHealthKey tags: [Health Keys] summary: Scan and verify a Health Key QR description: | The boarding-pass scan. Resolves the full transaction: identity, provider, HSO, state, bounds, funding. When this succeeds, negotiation stops. Supports offline verification: if the signed token is within its `offline_valid_until` window and the signature is valid, returns a VERIFIED result with `verification_mode: OFFLINE`. requestBody: required: true content: application/json: schema: type: object required: [qr_token] properties: qr_token: type: string scanner_provider_id: type: string format: uuid responses: '200': description: Health Key verified content: application/json: schema: $ref: '#/components/schemas/ScanResult' '400': description: Invalid or expired token content: application/json: schema: $ref: '#/components/schemas/Error' # ─── MEMBERSHIPS (mHSO) ──────────────────────────────────── /membership-tiers: get: operationId: listMembershipTiers tags: [Memberships] summary: Search membership tiers parameters: - name: template in: query schema: type: string enum: [DPC, PSYCHIATRY, MATERNITY, CHRONIC_SPECIALTY, CUSTOM] - name: geo in: query schema: type: string - name: radius_miles in: query schema: type: integer default: 25 - name: provider_id in: query schema: type: string format: uuid - name: hsa_eligible in: query schema: type: boolean - name: capacity_state in: query schema: type: string enum: [OPEN, WAITLIST, FULL] - $ref: '#/components/parameters/PageCursor' - $ref: '#/components/parameters/PageLimit' responses: '200': description: Membership tier list content: application/json: schema: $ref: '#/components/schemas/MembershipTier_List' post: operationId: createMembershipTier tags: [Memberships] summary: Create a membership tier description: | Provider creates a new membership tier with structured scope, pricing, capacity, and SLA definitions. Created in DRAFT state. **Validation enforced at creation:** - Required exclusions (EXCL.HOSPITAL.*, EXCL.ANES.GENERAL, EXCL.IMAGING.ADVANCED) must be present. Fails without them. - HSA-eligible tiers enforce IRS 2026-05 fee caps ($150/mo individual, $300/mo family) and prohibited inclusions. - Empty exclusion lists are rejected. This is a validation failure, not a warning. security: - ProviderAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/MembershipTierCreate' responses: '201': description: Tier created (DRAFT) content: application/json: schema: $ref: '#/components/schemas/MembershipTier' '400': description: | Validation failure. Check required_exclusions, fee caps (if HSA-eligible), and scope code validity. content: application/json: schema: $ref: '#/components/schemas/Error' /membership-tiers/{tier_id}: get: operationId: getMembershipTier tags: [Memberships] summary: Get tier detail parameters: - $ref: '#/components/parameters/TierID' responses: '200': description: Tier detail content: application/json: schema: $ref: '#/components/schemas/MembershipTier' patch: operationId: updateMembershipTier tags: [Memberships] summary: Update a membership tier description: | Editing a DRAFT tier modifies in-place (same version). Editing an ACTIVE tier creates version N+1 with state DRAFT. Existing members remain on the prior version — never auto-migrated. security: - ProviderAuth: [] parameters: - $ref: '#/components/parameters/TierID' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/MembershipTierUpdate' responses: '200': description: Tier updated (or new version created) content: application/json: schema: $ref: '#/components/schemas/MembershipTier' /membership-tiers/{tier_id}/activate: post: operationId: activateMembershipTier tags: [Memberships] summary: Activate a DRAFT tier for enrollment description: "Transitions DRAFT → ACTIVE. Final validation runs on activation." security: - ProviderAuth: [] parameters: - $ref: '#/components/parameters/TierID' responses: '200': description: Tier activated content: application/json: schema: $ref: '#/components/schemas/MembershipTier' /membership-tiers/{tier_id}/retire: post: operationId: retireMembershipTier tags: [Memberships] summary: Retire a tier description: | ACTIVE → RETIRED. No new enrollments. Existing members complete their current term on the locked version. security: - ProviderAuth: [] parameters: - $ref: '#/components/parameters/TierID' responses: '200': description: Tier retired content: application/json: schema: $ref: '#/components/schemas/MembershipTier' /memberships: post: operationId: createMembership tags: [Memberships] summary: Enroll patient in a membership tier description: | Creates a MembershipInstance. Triggers first period payment. On success, generates a membership Health Key. requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/MembershipCreate' responses: '201': description: Membership created content: application/json: schema: $ref: '#/components/schemas/MembershipInstance' /memberships/{membership_id}: get: operationId: getMembership tags: [Memberships] summary: Get membership detail parameters: - $ref: '#/components/parameters/MembershipID' responses: '200': description: Membership detail content: application/json: schema: $ref: '#/components/schemas/MembershipInstance' /memberships/{membership_id}/renew: post: operationId: renewMembership tags: [Memberships] summary: Renew a membership description: | For MANUAL renewal mode: patient explicitly renews. For AUTO: system calls this internally. Triggers payment for next period. Valid when state is RENEWAL_REQUIRED or ACTIVE (early renewal). parameters: - $ref: '#/components/parameters/MembershipID' requestBody: content: application/json: schema: type: object properties: payment_method_id: type: string description: Optional — uses stored method if omitted responses: '200': description: Membership renewed content: application/json: schema: $ref: '#/components/schemas/MembershipInstance' /memberships/{membership_id}/cancel: post: operationId: cancelMembership tags: [Memberships] summary: Cancel a membership parameters: - $ref: '#/components/parameters/MembershipID' requestBody: content: application/json: schema: type: object properties: reason: type: string responses: '200': description: Membership cancelled content: application/json: schema: $ref: '#/components/schemas/MembershipInstance' /memberships/{membership_id}/excluded-service: post: operationId: initiateExcludedService tags: [Memberships] summary: Initiate excluded-service handoff (Rail B) description: | Provider determines patient needs a service outside membership scope. System generates a standard atomic HSO checkout. Patient receives a new Health Key for the discrete transaction. The membership Health Key is unaffected. Both coexist. security: - ProviderAuth: [] parameters: - $ref: '#/components/parameters/MembershipID' requestBody: required: true content: application/json: schema: type: object required: [hso_id] properties: hso_id: type: string description: The excluded service HSO preferred_provider_id: type: string format: uuid description: Optional referral to specific provider notes: type: string maxLength: 500 responses: '201': description: | Excluded service initiated. Returns the offer candidates and a checkout URL for the patient. content: application/json: schema: type: object properties: referral_id: type: string format: uuid hso: $ref: '#/components/schemas/HSO' offer_candidates: type: array items: $ref: '#/components/schemas/Offer' patient_checkout_url: type: string format: uri # ─── FUNDING ──────────────────────────────────────────────── /health-keys/{health_key_id}/funding: get: operationId: getHealthKeyFunding tags: [Funding] summary: Get funding status parameters: - $ref: '#/components/parameters/HealthKeyID' responses: '200': description: Funding status content: application/json: schema: $ref: '#/components/schemas/FundingStatus' post: operationId: addFunding tags: [Funding] summary: Add a funding contribution description: | Adds a funding contribution. Each is held independently. **Checkout gate**: Health Key cannot transition from READY to ACTIVE until funding_state = FUNDED (pool >= required amount). **Withdrawal**: Contributions can be withdrawn only while funding_state is INITIATED or FUNDING_IN_PROGRESS. Once FUNDED, contributions are locked. parameters: - $ref: '#/components/parameters/HealthKeyID' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/FundingContribution' responses: '201': description: Contribution added content: application/json: schema: $ref: '#/components/schemas/FundingStatus' /funding-links: post: operationId: createFundingLink tags: [Funding] summary: Create a shareable funding link description: No account required to fund via link. requestBody: required: true content: application/json: schema: type: object required: [health_key_id] properties: health_key_id: type: string format: uuid suggested_amount_cents: type: integer minimum: 100 funder_type: type: string enum: [INDIVIDUAL, CHARITY] default: INDIVIDUAL expires_at: type: string format: date-time responses: '201': description: Funding link created content: application/json: schema: type: object properties: funding_link_id: type: string format: uuid url: type: string format: uri expires_at: type: string format: date-time # ─── SETTLEMENT ───────────────────────────────────────────── /health-keys/{health_key_id}/settlement: get: operationId: getSettlement tags: [Settlement] summary: Get settlement status parameters: - $ref: '#/components/parameters/HealthKeyID' responses: '200': description: Settlement status content: application/json: schema: $ref: '#/components/schemas/SettlementStatus' /health-keys/{health_key_id}/disputes: post: operationId: createDispute tags: [Settlement] summary: Open a dispute description: | Valid only after payment release, within the dispute window: - Visits / Tests: T0 + 7 calendar days - Surgery: T0 + 14 calendar days After window expiration, disputes are barred. Funds are final. parameters: - $ref: '#/components/parameters/HealthKeyID' requestBody: required: true content: application/json: schema: type: object required: [reason] properties: reason: type: string enum: - SERVICE_DID_NOT_OCCUR - WRONG_SERVICE - NO_SHOW_ATTRIBUTION_ERROR - DUPLICATE_CHARGE description: type: string maxLength: 1000 evidence_refs: type: array items: type: string responses: '201': description: Dispute opened content: application/json: schema: $ref: '#/components/schemas/Dispute' '400': description: Dispute window expired or invalid state content: application/json: schema: $ref: '#/components/schemas/Error' # ─── CONSENT ──────────────────────────────────────────────── /consent/grants: get: operationId: listConsentGrants tags: [Consent] summary: List consent grants for the current patient responses: '200': description: Consent grants content: application/json: schema: type: object properties: grants: type: array items: $ref: '#/components/schemas/ConsentGrant' post: operationId: requestConsentGrant tags: [Consent] summary: Request a consent grant requestBody: required: true content: application/json: schema: type: object required: [patient_id, tier, purpose] properties: patient_id: type: string format: uuid tier: $ref: '#/components/schemas/ConsentTier' purpose: type: string enum: [TRANSACTION_RESOLUTION, DATA_RETRIEVAL] grant_type: type: string enum: [PERSISTENT, SESSION_ONLY, ONE_SHOT] default: SESSION_ONLY scopes: type: array items: type: string responses: '200': description: Consent already granted content: application/json: schema: $ref: '#/components/schemas/ConsentGrant' '202': description: Patient approval required content: application/json: schema: type: object properties: status: type: string enum: [CONSENT_REQUIRED] consent_url: type: string format: uri grant_id: type: string format: uuid /consent/grants/{grant_id}/revoke: post: operationId: revokeConsentGrant tags: [Consent] summary: Revoke a consent grant (immediate) parameters: - name: grant_id in: path required: true schema: type: string format: uuid requestBody: content: application/json: schema: type: object properties: reason: type: string responses: '200': description: Grant revoked, all tokens invalidated content: application/json: schema: $ref: '#/components/schemas/ConsentGrant' # ─── AUDIT ────────────────────────────────────────────────── /audit/access-log: get: operationId: getAccessLog tags: [Audit] summary: Patient-visible access history description: | Returns a chronological log of all data access events for the authenticated patient: which client accessed what data, when, under which consent grant, using which tools/scopes. parameters: - name: client_id in: query schema: type: string format: uuid - name: since in: query schema: type: string format: date-time - $ref: '#/components/parameters/PageCursor' - $ref: '#/components/parameters/PageLimit' responses: '200': description: Access log content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/AccessLogEntry' pagination: $ref: '#/components/schemas/Pagination' # ─── WEBHOOKS ─────────────────────────────────────────────── /webhooks: get: operationId: listWebhooks tags: [Webhooks] summary: List webhook endpoints responses: '200': description: Webhooks content: application/json: schema: type: object properties: webhooks: type: array items: $ref: '#/components/schemas/WebhookEndpoint' post: operationId: createWebhook tags: [Webhooks] summary: Register a webhook endpoint requestBody: required: true content: application/json: schema: type: object required: [url, events] properties: url: type: string format: uri events: type: array items: $ref: '#/components/schemas/WebhookEventType' responses: '201': description: Webhook created (secret returned once) content: application/json: schema: $ref: '#/components/schemas/WebhookEndpoint' /webhooks/{webhook_id}: patch: operationId: updateWebhook tags: [Webhooks] summary: Update webhook URL or events parameters: - name: webhook_id in: path required: true schema: type: string format: uuid requestBody: required: true content: application/json: schema: type: object properties: url: type: string format: uri events: type: array items: $ref: '#/components/schemas/WebhookEventType' status: type: string enum: [ACTIVE, DISABLED] responses: '200': description: Webhook updated content: application/json: schema: $ref: '#/components/schemas/WebhookEndpoint' delete: operationId: deleteWebhook tags: [Webhooks] summary: Delete a webhook endpoint parameters: - name: webhook_id in: path required: true schema: type: string format: uuid responses: '204': description: Webhook deleted /webhooks/{webhook_id}/test: post: operationId: testWebhook tags: [Webhooks] summary: Send a test event parameters: - name: webhook_id in: path required: true schema: type: string format: uuid requestBody: content: application/json: schema: type: object properties: event_type: $ref: '#/components/schemas/WebhookEventType' responses: '200': description: Test event sent content: application/json: schema: type: object properties: delivered: type: boolean response_code: type: integer latency_ms: type: integer # ─── PLATFORM ─────────────────────────────────────────────── /clients: post: operationId: registerClient tags: [Platform] summary: Register a new platform client description: | Creates a client identity for API access. Starts in SANDBOX tier. Production access requires verification + security attestations. requestBody: required: true content: application/json: schema: type: object required: [name, redirect_uris, contact_email] properties: name: type: string description: type: string redirect_uris: type: array items: type: string format: uri contact_email: type: string format: email webhook_url: type: string format: uri responses: '201': description: Client created (credentials returned once) content: application/json: schema: type: object properties: client_id: type: string format: uuid client_secret: type: string description: Shown only at creation. Store securely. tier: type: string enum: [SANDBOX, PRODUCTION, ENTERPRISE] rate_limits: type: object properties: requests_per_minute: type: integer daily_tier2_budget: type: integer /clients/me: get: operationId: getClientInfo tags: [Platform] summary: Get current client details responses: '200': description: Client info content: application/json: schema: type: object properties: client_id: type: string format: uuid name: type: string tier: type: string enum: [SANDBOX, PRODUCTION, ENTERPRISE] rate_limits: type: object created_at: type: string format: date-time /clients/me/usage: get: operationId: getClientUsage tags: [Platform] summary: Get API usage statistics parameters: - name: period in: query schema: type: string enum: [today, this_week, this_month, last_30_days] default: this_month responses: '200': description: Usage stats content: application/json: schema: type: object properties: period: type: string total_requests: type: integer requests_by_endpoint: type: object additionalProperties: type: integer health_keys_created: type: integer transactions_settled: type: integer resolve_calls: type: integer # ═══════════════════════════════════════════════════════════════ # COMPONENTS # ═══════════════════════════════════════════════════════════════ components: securitySchemes: BearerAuth: type: http scheme: bearer description: Platform client API key PatientSession: type: http scheme: bearer description: Patient session token (OAuth 2.0 + PKCE) ProviderAuth: type: http scheme: bearer description: | Provider session token or platform delegation token with provider authorization parameters: HSO_ID: name: hso_id in: path required: true description: | Semantic dot-notation HSO identifier. Encodes taxonomy position. Immutable — identifies the service definition, not a database row. schema: type: string example: "hso.ortho.knee.proc.total_replacement" ProviderID: name: provider_id in: path required: true schema: type: string format: uuid HealthKeyID: name: health_key_id in: path required: true schema: type: string format: uuid PatientID: name: patient_id in: path required: true schema: type: string format: uuid OfferID: name: offer_id in: path required: true schema: type: string format: uuid TierID: name: tier_id in: path required: true schema: type: string format: uuid MembershipID: name: membership_id in: path required: true schema: type: string format: uuid PageCursor: name: cursor in: query schema: type: string PageLimit: name: limit in: query schema: type: integer default: 20 minimum: 1 maximum: 100 responses: BadRequest: description: Invalid request content: application/json: schema: $ref: '#/components/schemas/Error' Unauthorized: description: Missing or invalid authentication content: application/json: schema: $ref: '#/components/schemas/Error' NotFound: description: Resource not found content: application/json: schema: $ref: '#/components/schemas/Error' RateLimited: description: Rate limit exceeded content: application/json: schema: $ref: '#/components/schemas/Error' headers: Retry-After: schema: type: integer X-RateLimit-Limit: schema: type: integer X-RateLimit-Remaining: schema: type: integer # ─── SCHEMAS ──────────────────────────────────────────────── schemas: Error: type: object required: [code, message] properties: request_id: type: string format: uuid code: type: string enum: - INVALID_ARGUMENT - UNAUTHORIZED - CONSENT_REQUIRED - FORBIDDEN_SCOPE - NOT_FOUND - CONFLICT - INVALID_STATE_TRANSITION - RATE_LIMITED - UPSTREAM_UNAVAILABLE - TIMEOUT - INTERNAL - VALIDATION_FAILED message: type: string details: type: object additionalProperties: true Pagination: type: object properties: next_cursor: type: string nullable: true has_more: type: boolean total_count: type: integer # ── Taxonomy ────────────────────────────────────────────── Complaint: type: object required: [complaint_id, name] properties: complaint_id: type: string example: "complaint.knee_pain" name: type: string example: "Knee Pain" consumer_synonyms: type: array items: type: string example: ["my knee hurts", "knee ache", "sore knee"] specialty: type: string mapped_hso_ids: type: array items: type: string description: HSO candidates this complaint may resolve to discriminators: type: array items: type: object properties: id: type: string question: type: string type: type: string enum: [AGE_THRESHOLD, SEX_BASED, PREGNANCY_POSSIBLE, EXISTING_DIAGNOSIS, ACUITY, OTHER] description: | Discriminator questions that the CRE may use for routing. The HKIL can auto-resolve these from Health Key data. Diagnosis: type: object required: [diagnosis_id, name] properties: diagnosis_id: type: string example: "dx.osteoarthritis.knee" name: type: string consumer_synonyms: type: array items: type: string specialty: type: string mapped_hso_ids: type: array items: type: string care_phases: type: array items: type: string enum: [NEW_DIAGNOSIS, EVALUATION_FOLLOW_UP, EVALUATION_ESTABLISHED, PROCEDURAL] ScopeCode: type: object required: [code, label, type] properties: code: type: string example: "INCL.VISIT.OFFICE" label: type: string example: "Office visits" type: type: string enum: [INCLUSION, EXCLUSION] category: type: string example: "visit" description: type: string required_exclusion: type: boolean description: If true, must appear in every tier's excluded_scope # ── HSO ─────────────────────────────────────────────────── HSO: type: object required: [hso_id, hso_alias, name, specialty, subspecialty, category, specific] properties: hso_id: type: string description: | Semantic dot-notation identifier. Immutable. Encodes taxonomy position: `hso.{specialty}.{sub}.{cat}.{specific}` example: "hso.ortho.knee.proc.total_replacement" hso_alias: type: string example: "Total Knee Replacement" name: type: string specialty: type: string subspecialty: type: string category: type: string specific: type: string description: type: string scope: type: object properties: included: type: array items: type: string excluded: type: array items: type: string constraints: type: object properties: age_min: type: integer nullable: true age_max: type: integer nullable: true sex: type: string nullable: true enum: [M, F, ANY] requires_prior_hso: type: string nullable: true inputs_required: type: array items: type: string outputs_expected: type: array items: type: string medicare_exclusion: type: object properties: excluded: type: boolean exclusion_type: type: string nullable: true enum: [STATUTORY, COVERAGE_DETERMINATION] exclusion_reason: type: string nullable: true membership_eligible: type: boolean consumer_search_terms: type: array items: type: string active_offer_count: type: integer description: Number of active offers for this HSO HSO_List: type: object properties: data: type: array items: $ref: '#/components/schemas/HSO' pagination: $ref: '#/components/schemas/Pagination' # ── Provider ────────────────────────────────────────────── ProviderCreate: type: object required: [npi, display_name, specialty, locations] properties: npi: type: string pattern: '^\d{10}$' display_name: type: string legal_name: type: string specialty: type: string subspecialty: type: string credentials: type: array items: type: string locations: type: array minItems: 1 items: $ref: '#/components/schemas/ProviderLocationInput' accepting_new_patients: type: boolean default: true telehealth_available: type: boolean default: false ProviderUpdate: type: object properties: display_name: type: string accepting_new_patients: type: boolean telehealth_available: type: boolean locations: type: array items: $ref: '#/components/schemas/ProviderLocationInput' Provider: type: object required: [provider_id, display_name, npi, status] properties: provider_id: type: string format: uuid status: type: string enum: [PENDING, VERIFIED, SUSPENDED] display_name: type: string legal_name: type: string npi: type: string org_id: type: string provider_type: type: string enum: [INDIVIDUAL, ORGANIZATION] specialty: type: string subspecialty: type: string credentials: type: array items: type: string locations: type: array items: $ref: '#/components/schemas/ProviderLocation' capabilities: type: array items: type: string description: HSO IDs (populated from active offers) operational_signals: $ref: '#/components/schemas/OperationalSignals' medicare_opted_out: type: boolean accepting_new_patients: type: boolean telehealth_available: type: boolean created_at: type: string format: date-time ProviderLocationInput: type: object required: [name, address] properties: name: type: string address: type: object required: [street, city, state, postal_code] properties: street: type: string city: type: string state: type: string postal_code: type: string facility_type: type: string enum: [ASC, OFFICE, CLINIC, HOSPITAL_OUTPATIENT, OTHER] default: OFFICE ProviderLocation: type: object properties: location_id: type: string format: uuid name: type: string address: type: object properties: street: type: string city: type: string state: type: string postal_code: type: string geo: type: object properties: lat: type: number lng: type: number facility_type: type: string OperationalSignals: type: object description: Operational reliability. Not clinical. No star ratings. No prose reviews. properties: acceptance_rate: type: number minimum: 0 maximum: 1 cancellation_rate: type: number minimum: 0 maximum: 1 average_settlement_days: type: number dispute_rate: type: number minimum: 0 maximum: 1 transactability_score: type: integer minimum: 0 maximum: 100 Provider_List: type: object properties: data: type: array items: $ref: '#/components/schemas/Provider' pagination: $ref: '#/components/schemas/Pagination' # ── Offer ───────────────────────────────────────────────── OfferCreate: type: object required: [provider_id, hso_id, location_id, price_cents] properties: provider_id: type: string format: uuid hso_id: type: string location_id: type: string format: uuid price_cents: type: integer minimum: 100 description: | Cash price in cents. This is a commitment: "I will deliver this service for this amount regardless of insurance." binding_mode: type: string enum: [INDIVIDUALLY_BOUND, ROLE_BOUND, ORGANIZATION_BOUND, HYBRID_NAMED_LEAD] default: ORGANIZATION_BOUND availability: $ref: '#/components/schemas/AvailabilitySummary' AvailabilitySummary: type: object properties: next_available: type: string format: date slots_this_week: type: integer Offer: type: object required: [offer_id, provider_id, hso_id, location_id, price] properties: offer_id: type: string format: uuid provider_id: type: string format: uuid provider_display_name: type: string hso_id: type: string hso_name: type: string location_id: type: string format: uuid location: $ref: '#/components/schemas/ProviderLocation' price: type: object required: [amount_cents, currency] properties: amount_cents: type: integer example: 420000 currency: type: string default: "USD" immutable_once_accepted: type: boolean default: true description: | Cash Price Invariant: once a Health Key references this offer, the price at that moment is final for that transaction. Offer price changes apply prospectively only. availability: $ref: '#/components/schemas/AvailabilitySummary' distance_minutes: type: integer nullable: true transactability_score: type: integer minimum: 0 maximum: 100 binding_mode: type: string enum: [INDIVIDUALLY_BOUND, ROLE_BOUND, ORGANIZATION_BOUND, HYBRID_NAMED_LEAD] active: type: boolean created_at: type: string format: date-time Offer_List: type: object properties: data: type: array items: $ref: '#/components/schemas/Offer' pagination: $ref: '#/components/schemas/Pagination' # ── Resolve to Care ─────────────────────────────────────── ResolveRequest: type: object required: [intent_type, intent_id, geo] properties: patient_id: type: string format: uuid description: Optional. Enables HKIL personalization with consent. intent_type: type: string enum: [COMPLAINT, DIAGNOSIS, REQUESTED_SERVICE] intent_id: type: string description: Must match a valid ID from /taxonomy endpoints example: "complaint.knee_pain" geo: type: object required: [postal_code] properties: postal_code: type: string lat: type: number lng: type: number time_window: type: object properties: earliest_start: type: string format: date-time latest_start: type: string format: date-time financial_context: type: object description: | **Informational only.** Does NOT affect pricing or ranking. Exists to help platform partners display comparative context in their UIs (e.g. "your HSA balance can cover this"). All prices in the response are cash prices regardless of what is provided here. properties: hsa_balance_cents: type: integer description: Patient's HSA balance (for display comparison) annual_budget_remaining_cents: type: integer description: Employer/charity remaining budget (for display) patient_preferences: type: object properties: max_drive_minutes: type: integer gender_preference: type: string enum: [MALE, FEMALE, NO_PREFERENCE] language_preference: type: string existing_provider_ids: type: array items: type: string format: uuid clinical_context_refs: type: array items: type: string description: Reference IDs only. Requires Tier 2 consent. ResolveResponse: type: object required: - resolution_id - hso_candidates - provider_candidates - pricing - required_patient_confirmation properties: resolution_id: type: string format: uuid input_hash: type: string policy_version: type: string consent_tier_used: $ref: '#/components/schemas/ConsentTier' data_accessed_refs: type: array items: type: string hso_candidates: type: array items: type: object required: [hso_id, hso_label, confidence] properties: hso_id: type: string hso_label: type: string confidence: type: number minimum: 0 maximum: 1 reason_codes: type: array items: type: string provider_candidates: type: array items: type: object required: [provider_id, display_name, offer_id] properties: provider_id: type: string format: uuid display_name: type: string offer_id: type: string format: uuid availability_summary: $ref: '#/components/schemas/AvailabilitySummary' transactability_score: type: integer minimum: 0 maximum: 100 distance_minutes: type: integer reason_codes: type: array items: type: string pricing: type: object description: | **Always cash pricing. Always HIGH confidence.** There is no estimate, no deductible calculation, no allowed amount. The price is the cash price from the offer. properties: cash_price_cents: type: integer description: The cash price. Final. Deterministic. currency: type: string default: "USD" pricing_confidence: type: string enum: [HIGH] default: HIGH description: | Always HIGH for cash transactions. This field exists for schema completeness. It is never MEDIUM or LOW. confirmation_url: type: string format: uri description: Deep link for patient confirmation in Health Key UI required_patient_confirmation: type: boolean enum: [true] description: Always true. Non-negotiable. disclaimer_code: type: string enum: [NOT_MEDICAL_ADVICE] description: | Always NOT_MEDICAL_ADVICE. PRICING_ESTIMATE and ELIGIBILITY_DEPENDENT are removed — cash prices are not estimates and are not eligibility-dependent. # ── Patient ─────────────────────────────────────────────── PatientCreate: type: object required: [full_name, date_of_birth] properties: full_name: type: string date_of_birth: type: string format: date email: type: string format: email phone: type: string oauth_token: type: string description: | OAuth token from patient authentication flow. Used to verify identity and link to existing records. external_id: type: string description: Platform partner's ID for this patient (for correlation) Patient: type: object required: [patient_id, full_name, date_of_birth] properties: patient_id: type: string format: uuid full_name: type: string full_name_upper: type: string description: ALL CAPS rendering for Health Key display date_of_birth: type: string format: date email: type: string format: email phone: type: string external_id: type: string nullable: true created_at: type: string format: date-time # ── Health Key ──────────────────────────────────────────── HealthKeyCreate: type: object required: [patient_id, offer_id] properties: patient_id: type: string format: uuid offer_id: type: string format: uuid resolution_id: type: string format: uuid description: Audit trail link to resolve_to_care scheduled_at: type: string format: date-time HealthKey: type: object required: [health_key_id, patient, provider, service, state, exposure_boundary] properties: health_key_id: type: string format: uuid health_key_type: type: string enum: [EPISODIC, MEMBERSHIP] patient: type: object properties: patient_id: type: string format: uuid full_name: type: string description: ALL CAPS date_of_birth: type: string format: date provider: type: object properties: provider_id: type: string format: uuid display_name: type: string location: $ref: '#/components/schemas/ProviderLocation' service: type: object properties: hso_id: type: string hso_name: type: string description: Exact HSO registry name offer_id: type: string format: uuid scope: type: object properties: included: type: array items: type: string excluded: type: array items: type: string state: $ref: '#/components/schemas/HealthKeyState' exposure_boundary: type: object properties: max_patient_responsibility_cents: type: integer description: | The cash price. Largest element on screen. Emotional anchor of the Health Key. currency: type: string default: "USD" funding_state: $ref: '#/components/schemas/FundingState' service_state: $ref: '#/components/schemas/ServiceState' payment_state: $ref: '#/components/schemas/PaymentState' valid_transitions: type: array items: type: string description: | The set of states this Health Key can transition to from its current state. Computed server-side. example: ["ACTIVE", "CANCELLED"] scheduled_at: type: string format: date-time nullable: true qr_token: type: string description: Use /health-keys/{id}/qr to render created_at: type: string format: date-time updated_at: type: string format: date-time last_sync_at: type: string format: date-time HealthKeyState: type: string enum: [READY, ACTIVE, DECISION_REQUIRED, NOT_AUTHORIZED, COMPLETED, SETTLED, CANCELLED] description: | ## State Definitions - **READY**: Created, identity-only. Patient has not yet confirmed. - **ACTIVE**: Patient authorized. Service can proceed. QR scannable. - **DECISION_REQUIRED**: Re-consent needed (scope/price change). Amber. - **NOT_AUTHORIZED**: Execution blocked. Red. - **COMPLETED**: Service delivered, PoS confirmed. Settlement in progress. - **SETTLED**: Dispute window expired. Funds final. Terminal. - **CANCELLED**: Transaction cancelled. Terminal. ## Valid State Transitions ``` READY → ACTIVE (patient authorizes) READY → CANCELLED (patient or system cancels) ACTIVE → COMPLETED (Proof of Service attested) ACTIVE → DECISION_REQUIRED (scope/price change detected) ACTIVE → NOT_AUTHORIZED (funding lapse, provider withdrawal) ACTIVE → CANCELLED (patient, provider, or system cancels) DECISION_REQUIRED → ACTIVE (patient re-consents) DECISION_REQUIRED → NOT_AUTHORIZED (patient declines) DECISION_REQUIRED → CANCELLED (patient cancels) NOT_AUTHORIZED → READY (conditions restored, restart flow) NOT_AUTHORIZED → CANCELLED (abandoned) COMPLETED → SETTLED (dispute window expires, auto-transition) ``` Terminal states: SETTLED, CANCELLED. No transitions out. ## Relationship to ServiceState and PaymentState - READY: service=S1_SELECTED, payment=P1_AUTHORIZED - ACTIVE + scheduled: service=S2_SCHEDULED, payment=P2_ESCROWED - ACTIVE + service window: service=S3_PRE_SERVICE or S4_SERVICE_TIME - COMPLETED: service=S6_POS_CONFIRMED, payment=P4_RELEASED - SETTLED: service=S7_FINALIZED, payment=P5_SETTLED ServiceState: type: string enum: [S1_SELECTED, S2_SCHEDULED, S3_PRE_SERVICE, S4_SERVICE_TIME, S5_POS_EVALUATION, S6_POS_CONFIRMED, S7_FINALIZED] PaymentState: type: string enum: [P1_AUTHORIZED, P2_ESCROWED, P3_PENDING_POS, P4_RELEASED, P5_SETTLED] FundingState: type: string enum: - INITIATED - FUNDING_IN_PROGRESS - FUNDED - LOCKED - SERVICE_COMPLETED - SETTLED - REFUND_PENDING - REFUNDED - CANCELLED description: | ## Funding State Machine ``` INITIATED → FUNDING_IN_PROGRESS (first contribution added) FUNDING_IN_PROGRESS → FUNDED (pool >= required amount) FUNDING_IN_PROGRESS → CANCELLED (all contributions withdrawn) FUNDED → LOCKED (Health Key authorized) LOCKED → SERVICE_COMPLETED (PoS confirmed) SERVICE_COMPLETED → SETTLED (funds released to provider) SETTLED → REFUND_PENDING (overage detected) REFUND_PENDING → REFUNDED (refunds issued pro-rata) Any pre-LOCKED → CANCELLED (Health Key cancelled) ``` **FUNDED is the gate**: Health Key cannot authorize until funding_state = FUNDED. **LOCKED is irreversible**: Contributions cannot be withdrawn after LOCKED. QRPayload: type: object properties: health_key_id: type: string format: uuid patient_token: type: string description: Tokenized patient ID (no PHI) provider_id: type: string format: uuid hso_id: type: string state: $ref: '#/components/schemas/HealthKeyState' scope_hash: type: string description: Hash of included/excluded scope for integrity check signature: type: string description: Cryptographic signature issued_at: type: string format: date-time expires_at: type: string format: date-time offline_valid_until: type: string format: date-time description: | Offline verification window. If the current time is before this timestamp and the signature is valid, the QR can be verified without server connectivity. For clinic basements, ORs, and low-connectivity environments. last_sync_at: type: string format: date-time ScanResult: type: object required: [valid, health_key] properties: valid: type: boolean health_key: $ref: '#/components/schemas/HealthKey' scan_timestamp: type: string format: date-time verification_mode: type: string enum: [ONLINE, OFFLINE] description: | ONLINE: verified against server. OFFLINE: verified from signed token only (within offline_valid_until window). verification_status: type: string enum: - VERIFIED_ACTIVE - VERIFIED_DECISION_REQUIRED - NOT_AUTHORIZED - EXPIRED - INVALID_SIGNATURE - PROVIDER_MISMATCH - OFFLINE_AUTHORIZATION_VALID ProofOfService: type: object required: [pos_type] properties: pos_type: type: string enum: [TIME_BASED, EMR_CONNECTED, ARTIFACT_UPLOADED, EMR_PLUS_ARTIFACT, MANUAL_OVERRIDE] pos_tier: type: integer minimum: 0 maximum: 3 description: | Confidence tier. Determines dispute immunity. 3: EMR + artifact → near-irreversible 2: EMR only → partial immunity 1: Time-based → minimal immunity 0: Manual override → full dispute eligibility artifacts: type: array items: type: object properties: artifact_type: type: string enum: [ASSESSMENT_AND_PLAN, OPERATIVE_REPORT, TEST_RESULT, OTHER] reference_id: type: string attested_by: type: string completion_timestamp: type: string format: date-time # ── Membership ──────────────────────────────────────────── MembershipTierCreate: type: object required: - provider_id - name - template - fee_cents - fee_cadence - term_months - included_scope - excluded_scope properties: provider_id: type: string format: uuid name: type: string template: type: string enum: [DPC, PSYCHIATRY, MATERNITY, CHRONIC_SPECIALTY, CUSTOM] fee_cents: type: integer minimum: 100 fee_cadence: type: string enum: [MONTHLY, QUARTERLY, ANNUALLY] term_months: type: integer minimum: 1 auto_renew: type: boolean default: true max_members: type: integer waitlist_enabled: type: boolean default: false included_scope: type: array minItems: 1 items: type: string description: Scope codes from /taxonomy/scope-codes excluded_scope: type: array minItems: 3 items: type: string description: Must include required exclusions messaging_sla_hours: type: integer appointment_sla_days: type: integer cancellation_policy: type: object required: [type] properties: type: type: string enum: [NO_REFUND, PRORATED, FULL_REFUND_WITHIN_N_DAYS] refund_window_days: type: integer hsa_eligible: type: boolean default: false location_id: type: string format: uuid MembershipTierUpdate: type: object properties: name: type: string fee_cents: type: integer max_members: type: integer waitlist_enabled: type: boolean included_scope: type: array items: type: string excluded_scope: type: array items: type: string messaging_sla_hours: type: integer appointment_sla_days: type: integer MembershipTier: type: object required: [tier_id, provider_id, name, fee, term, included_scope, excluded_scope] properties: tier_id: type: string format: uuid version: type: integer tier_state: type: string enum: [DRAFT, ACTIVE, SUSPENDED, RETIRED, FULL] description: | DRAFT → ACTIVE (on activate) ACTIVE → SUSPENDED (provider pauses sales) ACTIVE → RETIRED (no new sales, existing complete term) ACTIVE → FULL (max_members reached, auto) SUSPENDED → ACTIVE (resume) FULL → ACTIVE (member cancels, capacity opens) provider_id: type: string format: uuid provider_display_name: type: string template: type: string enum: [DPC, PSYCHIATRY, MATERNITY, CHRONIC_SPECIALTY, CUSTOM] name: type: string description: type: string fee: type: object properties: amount_cents: type: integer cadence: type: string enum: [MONTHLY, QUARTERLY, ANNUALLY] currency: type: string default: "USD" term: type: object properties: duration_months: type: integer auto_renew: type: boolean mpr: type: object properties: total_cents: type: integer description: fee × periods in term. Computable. Deterministic. currency: type: string included_scope: type: array items: $ref: '#/components/schemas/ScopeCode' excluded_scope: type: array items: $ref: '#/components/schemas/ScopeCode' capacity: type: object properties: max_members: type: integer current_members: type: integer waitlist_enabled: type: boolean waitlist_count: type: integer sla: type: object properties: messaging_response_hours: type: integer appointment_within_days: type: integer cancellation_policy: type: object properties: type: type: string enum: [NO_REFUND, PRORATED, FULL_REFUND_WITHIN_N_DAYS] refund_window_days: type: integer nullable: true hsa_eligible: type: boolean location: $ref: '#/components/schemas/ProviderLocation' not_insurance_disclosure: type: string default: "This membership is not insurance and does not replace health insurance coverage." MembershipTier_List: type: object properties: data: type: array items: $ref: '#/components/schemas/MembershipTier' pagination: $ref: '#/components/schemas/Pagination' MembershipCreate: type: object required: [patient_id, tier_id, payment_method_id] properties: patient_id: type: string format: uuid tier_id: type: string format: uuid payment_method_id: type: string renewal_mode: type: string enum: [AUTO, MANUAL] default: AUTO MembershipInstance: type: object required: [membership_id, patient_id, tier_id, state] properties: membership_id: type: string format: uuid patient_id: type: string format: uuid tier_id: type: string format: uuid tier_version: type: integer state: type: string enum: [ACTIVE, GRACE_PERIOD, RENEWAL_REQUIRED, LAPSED, CANCELLED] description: | ACTIVE → GRACE_PERIOD (payment fails, within grace window) ACTIVE → RENEWAL_REQUIRED (term ending, manual renewal mode) GRACE_PERIOD → ACTIVE (payment succeeds during grace) GRACE_PERIOD → LAPSED (grace expires without payment) RENEWAL_REQUIRED → ACTIVE (patient renews) RENEWAL_REQUIRED → LAPSED (renewal window expires) LAPSED → CANCELLED (recovery window expires) Any → CANCELLED (patient or system cancels) term_start: type: string format: date term_end: type: string format: date current_period_start: type: string format: date current_period_end: type: string format: date current_period_payment_status: type: string enum: [PAID, PAST_DUE, GRACE] renewal_mode: type: string enum: [AUTO, MANUAL] health_key_id: type: string format: uuid mpr: type: object properties: total_cents: type: integer paid_cents: type: integer remaining_cents: type: integer created_at: type: string format: date-time # ── Funding ─────────────────────────────────────────────── FundingContribution: type: object required: [source_type, amount_cents, payment_method_token] properties: source_type: type: string enum: [PATIENT, INDIVIDUAL, CHARITY, EMPLOYER, BNPL] amount_cents: type: integer minimum: 100 currency: type: string default: "USD" funder_id: type: string format: uuid nullable: true description: For INDIVIDUAL/CHARITY/EMPLOYER payment_method_token: type: string metadata: type: object additionalProperties: true FundingStatus: type: object properties: health_key_id: type: string format: uuid state: $ref: '#/components/schemas/FundingState' required_upfront_cents: type: integer description: The cash price. Checkout gate. total_funded_cents: type: integer remaining_cents: type: integer contributions: type: array items: type: object properties: contribution_id: type: string format: uuid source_type: type: string enum: [PATIENT, INDIVIDUAL, CHARITY, EMPLOYER, BNPL] amount_cents: type: integer escrow_status: type: string enum: [PENDING, ESCROWED, RELEASED, REFUNDED] refund_amount_cents: type: integer nullable: true funder_display_name: type: string nullable: true created_at: type: string format: date-time # ── Settlement ──────────────────────────────────────────── SettlementStatus: type: object properties: health_key_id: type: string format: uuid payment_state: $ref: '#/components/schemas/PaymentState' service_state: $ref: '#/components/schemas/ServiceState' escrow: type: object properties: total_escrowed_cents: type: integer created_at: type: string format: date-time release: type: object nullable: true properties: released_cents: type: integer released_at: type: string format: date-time description: T0 — dispute window starts pos_tier: type: integer minimum: 0 maximum: 3 dispute_window: type: object nullable: true properties: opens_at: type: string format: date-time closes_at: type: string format: date-time is_open: type: boolean finality: type: object nullable: true properties: finalized_at: type: string format: date-time is_final: type: boolean Dispute: type: object properties: dispute_id: type: string format: uuid health_key_id: type: string format: uuid reason: type: string enum: [SERVICE_DID_NOT_OCCUR, WRONG_SERVICE, NO_SHOW_ATTRIBUTION_ERROR, DUPLICATE_CHARGE] status: type: string enum: [OPEN, AUTO_RESOLVED, UNDER_REVIEW, ARBITRATION, UPHELD, DENIED] immunity_tier: type: integer minimum: 0 maximum: 3 created_at: type: string format: date-time resolution: type: object nullable: true properties: outcome: type: string enum: [REFUND, PARTIAL_REFUND, DENIED] refund_cents: type: integer nullable: true resolved_at: type: string format: date-time # ── Consent ─────────────────────────────────────────────── ConsentTier: type: string enum: [TIER1, TIER2, TIER3] description: | TIER1: Identity + summary (default, minimum necessary for resolution). TIER2: Medications, conditions, labs, allergies (opt-in). TIER3: Encounter history, sensitive longitudinal data (opt-in, restricted). ConsentGrant: type: object properties: grant_id: type: string format: uuid patient_id: type: string format: uuid client_id: type: string format: uuid tier: $ref: '#/components/schemas/ConsentTier' grant_type: type: string enum: [PERSISTENT, SESSION_ONLY, ONE_SHOT] purpose: type: string enum: [TRANSACTION_RESOLUTION, DATA_RETRIEVAL] scopes: type: array items: type: string status: type: string enum: [PENDING_APPROVAL, ACTIVE, SUSPENDED, REVOKED, EXPIRED] created_at: type: string format: date-time expires_at: type: string format: date-time nullable: true revoked_at: type: string format: date-time nullable: true # ── Audit ───────────────────────────────────────────────── AccessLogEntry: type: object properties: entry_id: type: string format: uuid client_id: type: string format: uuid client_name: type: string consent_grant_id: type: string format: uuid tier: $ref: '#/components/schemas/ConsentTier' tools_invoked: type: array items: type: string data_categories_accessed: type: array items: type: string timestamp: type: string format: date-time # ── Webhooks ────────────────────────────────────────────── WebhookEndpoint: type: object properties: webhook_id: type: string format: uuid url: type: string format: uri events: type: array items: $ref: '#/components/schemas/WebhookEventType' secret: type: string description: HMAC signing secret (shown only at creation) status: type: string enum: [ACTIVE, DISABLED] created_at: type: string format: date-time WebhookEventType: type: string enum: - health_key.created - health_key.authorized - health_key.decision_required - health_key.not_authorized - health_key.completed - health_key.settled - health_key.cancelled - settlement.escrowed - settlement.released - settlement.finalized - funding.contribution_added - funding.funded - funding.locked - funding.refund_issued - dispute.opened - dispute.resolved - membership.created - membership.renewed - membership.grace_period - membership.lapsed - membership.cancelled - membership_tier.activated - membership_tier.retired - offer.created - offer.updated - offer.retired - consent.granted - consent.revoked - provider.verified