{"openapi":"3.1.0","info":{"title":"Profile Health API","version":"1.0.0","description":"Unified healthcare data API for third-party partners.\n\nAuthentication: OAuth 2.0 client_credentials (Phase 2).\nAll PHI is encrypted in transit (TLS 1.2+) and at rest.\nHIPAA audit logging is applied to all PHI-adjacent endpoints.\n\nGraphQL is the primary interface; this REST façade is provided\nfor partners that cannot consume GraphQL. See ADR-0001.\n","contact":{"name":"Profile Health Engineering","email":"engineering@profilehealth.com"},"license":{"name":"Proprietary"}},"servers":[{"url":"https://api.profilehealth.com/v1","description":"Production"},{"url":"https://api-staging.profilehealth.com/v1","description":"Staging"},{"url":"http://localhost:3001/v1","description":"Local development"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}},"headers":{"X-Correlation-Id":{"description":"Opaque request trace ID. Include in support requests.","schema":{"type":"string","format":"uuid"}},"Idempotency-Replay":{"description":"Present on mutation responses that the gateway served from its\nIdempotency-Key cache rather than executing the operation again.\nValue is always the literal string `true` when set; the header is\nabsent on the first (executing) call. Partners can use it to\ndistinguish a fresh execution from a cached replay, e.g. for\nclient-side analytics or to skip post-success follow-ups already\nkicked off on the original call.\n","schema":{"type":"string","enum":["true"]}},"Sunset":{"description":"Date after which this endpoint will be removed (RFC 8594).","schema":{"type":"string","format":"date"}}},"schemas":{"HealthStatus":{"type":"object","required":["status","version","gitSha","timestamp","upstreams"],"properties":{"status":{"type":"string","enum":["ok","degraded"],"description":"`degraded` when at least one upstream circuit breaker is not `closed`, i.e. the gateway is up but calls through that upstream are failing or stalled. `half-open` counts: it is reached only after the breaker tripped, and callers park on a single probe until it resolves. The HTTP status is 200 either way — this endpoint is the readiness probe, and failing it during an upstream outage would withdraw the instances that are still serving."},"version":{"type":"string","description":"npm package version"},"gitSha":{"type":"string","description":"Git commit SHA at build time"},"timestamp":{"type":"string","format":"date-time"},"upstreams":{"type":"array","description":"Circuit breaker state per upstream. An upstream appears only once it has been called since the instance started, so an absent name means \"not exercised on this instance\", not \"healthy\".","items":{"type":"object","required":["upstream","state"],"properties":{"upstream":{"type":"string","example":"medplum"},"state":{"type":"string","enum":["closed","open","half-open","isolated"]}}}}}},"Problem":{"description":"RFC 7807 problem detail","type":"object","required":["type","title","status","detail","correlationId"],"properties":{"type":{"type":"string","format":"uri","description":"Problem type URI (https://errors.profilehealth.com/*)"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"correlationId":{"type":"string","format":"uuid"},"instance":{"type":"string","format":"uri"},"violations":{"type":"array","description":"Present on some `VALIDATION_FAILED` responses (kit operations, which\nare GraphQL-only — see ADR-0024 v2): the fields that failed and the\nrule each broke. Field names and rule codes only — never values.\n","items":{"$ref":"#/components/schemas/KitActivationViolation"}}}},"PageInfo":{"type":"object","required":["hasNextPage","hasPreviousPage"],"properties":{"hasNextPage":{"type":"boolean"},"hasPreviousPage":{"type":"boolean"},"startCursor":{"type":"string"},"endCursor":{"type":"string"},"totalCount":{"type":"integer"}}},"IntakeInvitationInput":{"description":"Patient intake invitation request. Either `name` OR\n(`firstName` + `lastName`) must be supplied.\n","type":"object","required":["email","clinicId"],"properties":{"email":{"type":"string","format":"email"},"name":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"phone":{"type":"string"},"clinicId":{"type":"string","description":"FHIR Organization id (must be in the partner's allowedClinicIds)"},"expiresInDays":{"type":"integer","minimum":1,"maximum":30,"default":7},"useMockData":{"type":"boolean","default":true,"description":"Smoke-test shortcut for partner integration runs. When `true` AND\nthe patient has no existing onboarding progress, the intake\nwizard prefills every required PersonalInfoStep field with a\ncanned profile and lands the wizard on PersonalInfoStep — the\npatient taps Continue twice (once on personal-info, once on\nlegal-forms) and CompletionStep auto-submits. Real progress\n(server-side or local-encrypted) takes precedence; the flag\nnever clobbers data a patient already entered.\n\nProduction partners should leave this unset or `false`. Defaults\nto `true` on the Cloud Function side for the admin-app create\npath; partner-API callers must opt out explicitly.\n"}}},"IntakeInvitation":{"type":"object","required":["id","tokenString","url","expiresAt","email","name","clinicId","createdAt","status"],"properties":{"id":{"type":"string"},"tokenString":{"type":"string"},"url":{"type":"string","format":"uri","description":"Patient-facing onboarding URL"},"expiresAt":{"type":"string","format":"date-time"},"email":{"type":"string","format":"email"},"name":{"type":"string"},"clinicId":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"status":{"type":"string","enum":["pending"]}}},"KitActivationViolation":{"type":"object","required":["field","rule"],"properties":{"field":{"type":"string","description":"Dotted path into the input, e.g. `sampleId`, `grants[1].version`, `rows[3].dateOfBirth`."},"rule":{"type":"string","enum":["INVALID_KIT_CODE","INVALID_DATE_OF_BIRTH","NAME_REQUIRED","INVALID_EMAIL","INVALID_ORDER_REF","DUPLICATE_SAMPLE_ID","DUPLICATE_ORDER_REF","NO_ROWS","TOO_MANY_ROWS","GRANT_MISSING","GRANT_DUPLICATE","GRANT_UNKNOWN_KIND","CONSENT_REQUIRED","CONSENT_VERSION_NOT_ACCEPTED","INVALID_CLIENT_CONTEXT"]}}},"Coding":{"type":"object","properties":{"system":{"type":"string"},"code":{"type":"string"},"display":{"type":"string"}}},"CodeableConcept":{"type":"object","properties":{"coding":{"type":"array","items":{"$ref":"#/components/schemas/Coding"}},"text":{"type":"string"}}},"Reference":{"type":"object","required":["reference"],"properties":{"reference":{"type":"string","description":"FHIR relative reference, e.g. Patient/p-1"},"display":{"type":"string"}}},"Period":{"type":"object","properties":{"start":{"type":"string","format":"date-time"},"end":{"type":"string","format":"date-time"}}},"EncounterStatus":{"type":"string","enum":["PLANNED","ARRIVED","TRIAGED","IN_PROGRESS","ONLEAVE","FINISHED","CANCELLED","ENTERED_IN_ERROR","UNKNOWN"]},"EncounterParticipant":{"type":"object","properties":{"type":{"type":"array","items":{"$ref":"#/components/schemas/CodeableConcept"}},"individual":{"$ref":"#/components/schemas/Reference"},"period":{"$ref":"#/components/schemas/Period"}}},"Encounter":{"description":"FHIR R4 Encounter, flattened. Lifecycle is enforced server-side\nper ADR-0017 §5 — re-opening a finished encounter is rejected.\n","type":"object","required":["id","status","type","subject"],"properties":{"id":{"type":"string","description":"Medplum-assigned Encounter id (UUID).","example":"7c1f9b3d-2a4e-4d8b-9012-aa6e3f8d1c22"},"status":{"allOf":[{"$ref":"#/components/schemas/EncounterStatus"}],"description":"Lifecycle status. See `EncounterStatus`."},"class":{"allOf":[{"$ref":"#/components/schemas/Coding"}],"description":"FHIR class Coding — typically `AMB` (ambulatory), `IMP` (inpatient), `EMER` (emergency)."},"type":{"type":"array","description":"Encounter.type — at least one entry. Usually a LOINC consultation code.","items":{"$ref":"#/components/schemas/CodeableConcept"}},"subject":{"allOf":[{"$ref":"#/components/schemas/Reference"}],"description":"`Patient/{id}` reference. Always populated."},"period":{"allOf":[{"$ref":"#/components/schemas/Period"}],"description":"Visit timing (`start`/`end` ISO 8601). Null when timing has not been recorded."},"participant":{"type":"array","description":"Practitioners, Devices, or RelatedPersons participating in the visit.","items":{"$ref":"#/components/schemas/EncounterParticipant"}},"basedOn":{"type":"array","description":"`QuestionnaireResponse/{id}` references for intake forms that initiated this encounter.","items":{"$ref":"#/components/schemas/Reference"}},"reasonCode":{"type":"array","description":"SNOMED/ICD coded reasons. Free for partners to populate; not required.","items":{"$ref":"#/components/schemas/CodeableConcept"}},"serviceProvider":{"allOf":[{"$ref":"#/components/schemas/Reference"}],"description":"`Organization/{id}` of the providing facility."},"versionId":{"type":"string","description":"FHIR ETag (`Encounter.meta.versionId`). Pass to `ifMatch` for optimistic concurrency.","example":"3"},"lastUpdated":{"type":"string","format":"date-time","description":"ISO 8601 of the last write (`Encounter.meta.lastUpdated`)."}}},"EncounterEdge":{"type":"object","required":["node","cursor"],"properties":{"node":{"$ref":"#/components/schemas/Encounter"},"cursor":{"type":"string"}}},"EncounterConnection":{"type":"object","required":["edges","pageInfo"],"properties":{"edges":{"type":"array","items":{"$ref":"#/components/schemas/EncounterEdge"}},"pageInfo":{"$ref":"#/components/schemas/PageInfo"}}},"CreateEncounterInput":{"description":"Encounter create payload. The `patientId` must reference a Patient\nin one of the partner's `allowedClinicIds`. Defaults applied when\nomitted: status=IN_PROGRESS, class=AMB (ambulatory), type=Consultation\n(LOINC LP173421-1), period.start=now (server clock).\n","type":"object","required":["patientId"],"properties":{"patientId":{"type":"string","description":"Patient id (the resource id, not the full FHIR reference). Must be in one of the caller's allowed clinics.","example":"0991c6ab-aea2-486b-8b79-a385b51eddf0"},"status":{"allOf":[{"$ref":"#/components/schemas/EncounterStatus"}],"description":"Initial lifecycle status. Defaults to `IN_PROGRESS` when omitted."},"classCode":{"type":"string","description":"FHIR Encounter.class code (HL7 v3 ActCode). Defaults to `AMB` (ambulatory).","example":"AMB"},"classSystem":{"type":"string","description":"Coding system URL for `classCode`. Defaults to the v3 ActCode terminology.","example":"http://terminology.hl7.org/CodeSystem/v3-ActCode"},"classDisplay":{"type":"string","description":"Human-readable label for `classCode` (rendered in some EHR UIs).","example":"ambulatory"},"type":{"type":"array","description":"Encounter.type — at least one entry. Defaults to LOINC `LP173421-1` (Consultation) when omitted.","items":{"$ref":"#/components/schemas/CodeableConcept"}},"periodStart":{"type":"string","format":"date-time","description":"ISO 8601 start of the encounter. Defaults to the server clock when omitted."},"periodEnd":{"type":"string","format":"date-time","description":"ISO 8601 end. Optional on create; usually set later via update or sign."},"participantPractitionerIds":{"type":"array","description":"Practitioner ids participating in the visit (each becomes a participant entry with the calling principal as default).","items":{"type":"string"}},"basedOnQuestionnaireResponseIds":{"type":"array","description":"`QuestionnaireResponse/{id}` ids the encounter is based on (e.g. an intake form).","items":{"type":"string"}},"reasonCode":{"type":"array","description":"SNOMED/ICD coded reasons for the visit.","items":{"$ref":"#/components/schemas/CodeableConcept"}},"serviceProviderOrganizationId":{"type":"string","description":"`Organization/{id}` providing the service."}}},"UpdateEncounterInput":{"description":"Partial update — only fields you supply are written. Status transitions\nare FHIR-conformant per ADR-0017 (planned → in-progress → finished,\nwith cancelled as soft-delete). Re-opening a finished encounter is rejected.\n","type":"object","properties":{"status":{"allOf":[{"$ref":"#/components/schemas/EncounterStatus"}],"description":"New lifecycle status. Server validates the transition."},"classCode":{"type":"string","description":"New encounter class code. Send all three (`classCode`, `classSystem`, `classDisplay`) together when changing class."},"classSystem":{"type":"string","description":"Coding system URL for `classCode`."},"classDisplay":{"type":"string","description":"Human-readable label for `classCode`."},"type":{"type":"array","description":"New Encounter.type — replaces the existing array if supplied.","items":{"$ref":"#/components/schemas/CodeableConcept"}},"periodStart":{"type":"string","format":"date-time","description":"New ISO 8601 start of the encounter."},"periodEnd":{"type":"string","format":"date-time","description":"ISO 8601 end. Setting this is the typical signal that the visit has ended."},"participantPractitionerIds":{"type":"array","description":"Replaces the participant list entirely if supplied.","items":{"type":"string"}},"basedOnQuestionnaireResponseIds":{"type":"array","description":"Replaces the basedOn list entirely if supplied.","items":{"type":"string"}},"reasonCode":{"type":"array","description":"Replaces the reasonCode list entirely if supplied.","items":{"$ref":"#/components/schemas/CodeableConcept"}},"serviceProviderOrganizationId":{"type":"string","description":"New service-provider Organization id."},"ifMatch":{"type":"string","description":"Optional FHIR ETag (`W/\"<versionId>\"`) for optimistic concurrency. Server returns 412 (Precondition Failed) on mismatch.","example":"W/\"3\""}}},"DeleteEncounterInput":{"description":"Soft-delete payload — sets Encounter.status to CANCELLED.","type":"object","required":["reason"],"properties":{"reason":{"type":"string","minLength":1,"description":"Audit-required free-text justification."}}},"CompositionStatus":{"type":"string","enum":["PRELIMINARY","FINAL","AMENDED","ENTERED_IN_ERROR"]},"SoapSection":{"type":"string","enum":["SUBJECTIVE","OBJECTIVE","ASSESSMENT","PLAN"]},"Narrative":{"type":"object","required":["status","div"],"properties":{"status":{"type":"string"},"div":{"type":"string","description":"Sanitized XHTML wrapped in `<div xmlns=\"http://www.w3.org/1999/xhtml\">`."}}},"CompositionSection":{"type":"object","required":["code"],"properties":{"title":{"type":"string"},"code":{"$ref":"#/components/schemas/CodeableConcept"},"text":{"$ref":"#/components/schemas/Narrative"}}},"CompositionAttester":{"type":"object","required":["mode"],"properties":{"mode":{"type":"string","enum":["personal","professional","legal","official"]},"time":{"type":"string","format":"date-time"},"party":{"$ref":"#/components/schemas/Reference"}}},"CompositionRelatesTo":{"type":"object","required":["code"],"properties":{"code":{"type":"string","enum":["replaces","transforms","signs","appends"]},"targetReference":{"$ref":"#/components/schemas/Reference"}}},"Composition":{"description":"FHIR R4 Composition (SOAP note). Status `AMENDED` on the wire indicates\na `final` Composition that carries `relatesTo[code=replaces]` to its\npredecessor (ADR-0017 §3).\n","type":"object","required":["id","status","type","subject","date","author","section"],"properties":{"id":{"type":"string","description":"Medplum-assigned Composition id.","example":"9f8c2b41-5d3e-47a0-b289-c5e1f4d9a7b3"},"status":{"allOf":[{"$ref":"#/components/schemas/CompositionStatus"}],"description":"Lifecycle status. See `CompositionStatus`."},"type":{"allOf":[{"$ref":"#/components/schemas/CodeableConcept"}],"description":"Note kind (e.g. LOINC `34117-2` \"History and physical note\"). Defaults injected on create."},"subject":{"allOf":[{"$ref":"#/components/schemas/Reference"}],"description":"`Patient/{id}` reference. Always populated."},"encounter":{"allOf":[{"$ref":"#/components/schemas/Reference"}],"description":"`Encounter/{id}` the note is attached to."},"author":{"type":"array","description":"One or more `Practitioner/{id}` authors. Defaults to the calling principal.","items":{"$ref":"#/components/schemas/Reference"}},"date":{"type":"string","format":"date-time","description":"ISO 8601 — note creation date (`Composition.date`)."},"title":{"type":"string","description":"Free-text title. Null when not set."},"section":{"type":"array","description":"Sections (≥1). Canonical SOAP sections plus any custom.","items":{"$ref":"#/components/schemas/CompositionSection"}},"attester":{"type":"array","description":"Attester records — populated by the sign endpoint.","items":{"$ref":"#/components/schemas/CompositionAttester"}},"relatesTo":{"type":"array","description":"`relatesTo` chain. `code=replaces` linking to the predecessor on amendments.","items":{"$ref":"#/components/schemas/CompositionRelatesTo"}},"versionId":{"type":"string","description":"FHIR ETag (`Composition.meta.versionId`). Use for optimistic concurrency."},"lastUpdated":{"type":"string","format":"date-time","description":"ISO 8601 of the last write (`Composition.meta.lastUpdated`)."}}},"CompositionEdge":{"type":"object","required":["node","cursor"],"properties":{"node":{"$ref":"#/components/schemas/Composition"},"cursor":{"type":"string"}}},"CompositionConnection":{"type":"object","required":["edges","pageInfo"],"properties":{"edges":{"type":"array","items":{"$ref":"#/components/schemas/CompositionEdge"}},"pageInfo":{"$ref":"#/components/schemas/PageInfo"}}},"CompositionSectionInput":{"description":"Either `soap` (canonical SOAP taxonomy applied automatically) OR\nan explicit `code` for a custom section.\n","type":"object","properties":{"soap":{"$ref":"#/components/schemas/SoapSection"},"code":{"$ref":"#/components/schemas/CodeableConcept"},"title":{"type":"string"},"narrative":{"type":"string"}}},"CreateCompositionInput":{"description":"Composition create payload. `patientId` must reference a Patient in\nthe partner's `allowedClinicIds`. `encounterId` must reference a\nnon-cancelled Encounter for the same Patient. Defaults: type=LOINC\n34117-2 (H&P note), date=now, status=PRELIMINARY. At least one\nsection is required; canonical SOAP completeness is enforced at\nsign time, not at create.\n","type":"object","required":["patientId","encounterId","section"],"properties":{"patientId":{"type":"string","description":"Patient id (resource id only, not the full FHIR reference).","example":"0991c6ab-aea2-486b-8b79-a385b51eddf0"},"encounterId":{"type":"string","description":"Encounter id. Must reference a non-cancelled Encounter for the same patient.","example":"7c1f9b3d-2a4e-4d8b-9012-aa6e3f8d1c22"},"type":{"allOf":[{"$ref":"#/components/schemas/CodeableConcept"}],"description":"Note kind. Defaults to LOINC `34117-2` (History and physical note) when omitted."},"title":{"type":"string","description":"Free-text title rendered in EHR UIs. Optional.","example":"Follow-up — hypertension"},"authorPractitionerIds":{"type":"array","description":"Explicit author Practitioner ids. Defaults to the calling principal when omitted.","items":{"type":"string"}},"section":{"type":"array","minItems":1,"description":"At least one section. Use `soap` for canonical SOAP semantics or `code` for custom sections.","items":{"$ref":"#/components/schemas/CompositionSectionInput"}}}},"UpdateCompositionSectionInput":{"description":"Replace one canonical SOAP section's narrative on a PRELIMINARY note.","type":"object","required":["narrative"],"properties":{"narrative":{"type":"string","description":"New narrative text. Plain text or simple HTML; the gateway sanitizes on write.","example":"BP 122/80, HR 68. Auscultation clear bilaterally."},"ifMatch":{"type":"string","description":"Optional FHIR ETag (`W/\"<versionId>\"`) for optimistic concurrency. Server returns 412 on mismatch.","example":"W/\"3\""}}},"SoapSectionUpdate":{"type":"object","required":["section","narrative"],"properties":{"section":{"$ref":"#/components/schemas/SoapSection"},"narrative":{"type":"string"}}},"AmendCompositionInput":{"description":"Amendment patch — applied to a copy of the FINAL note. Each\nsection update overlays the corresponding section in the new\nPRELIMINARY draft. The amendment must be signed separately.\n","type":"object","properties":{"sectionUpdates":{"type":"array","items":{"$ref":"#/components/schemas/SoapSectionUpdate"}},"title":{"type":"string"}}},"DeleteCompositionInput":{"description":"Soft-delete payload. On a FINAL Composition, `force=true` is\nrequired; otherwise a 422 VALIDATION_FAILED is returned.\n","type":"object","required":["reason"],"properties":{"reason":{"type":"string","minLength":1},"force":{"type":"boolean","default":false}}},"ScratchNoteVisibility":{"type":"string","enum":["PERSONAL","CLINIC"],"description":"PERSONAL — author-only (cross-author reads return 404 unless\nthe caller carries `admin:scratch-notes:read`). CLINIC — visible\nto any clinic-allowed reader. Reserved for internal principals\n(PERSONAL); partner principals are restricted to CLINIC.\n"},"ScratchNote":{"description":"Clinician working note about a patient — NOT part of the formal\nmedical record. Stored as FHIR R4 `Basic` with code `scratch-note`.\n","type":"object","required":["id","subject","author","text","visibility","createdAt","deleted"],"properties":{"id":{"type":"string","description":"Medplum-assigned Basic resource id."},"subject":{"allOf":[{"$ref":"#/components/schemas/Reference"}],"description":"`Patient/{id}` reference."},"author":{"allOf":[{"$ref":"#/components/schemas/Reference"}],"description":"`Practitioner/{id}` author. Set on create, immutable thereafter."},"text":{"type":"string","maxLength":4096,"description":"Free text body. Plain text — no markdown rendering on the gateway side."},"visibility":{"allOf":[{"$ref":"#/components/schemas/ScratchNoteVisibility"}],"description":"Visibility scope. See `ScratchNoteVisibility`."},"createdAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp of creation."},"updatedAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp of the last edit. Null until the first update."},"versionId":{"type":"string","description":"FHIR ETag (`Basic.meta.versionId`)."},"deleted":{"type":"boolean","description":"True only when the note carries the entered-in-error tag."}}},"ScratchNoteEdge":{"type":"object","required":["node","cursor"],"properties":{"node":{"$ref":"#/components/schemas/ScratchNote"},"cursor":{"type":"string"}}},"ScratchNoteConnection":{"type":"object","required":["edges","pageInfo"],"properties":{"edges":{"type":"array","items":{"$ref":"#/components/schemas/ScratchNoteEdge"}},"pageInfo":{"$ref":"#/components/schemas/PageInfo"}}},"CreateScratchNoteInput":{"description":"Scratch note create payload. Author defaults to the calling\nprincipal Practitioner. Visibility defaults to CLINIC; PERSONAL\nis reserved for internal principals.\n","type":"object","required":["patientId","text"],"properties":{"patientId":{"type":"string","description":"Patient id (resource id only). Must be in the caller's allowed clinics.","example":"0991c6ab-aea2-486b-8b79-a385b51eddf0"},"text":{"type":"string","minLength":1,"maxLength":4096,"description":"Free text body. Trimmed and length-checked server-side."},"visibility":{"allOf":[{"$ref":"#/components/schemas/ScratchNoteVisibility"}],"description":"Optional. Defaults to CLINIC. Partner principals may not set PERSONAL."}}},"UpdateScratchNoteInput":{"description":"Patch payload for a scratch note. Subject and author are\nimmutable; only the original author may edit.\n","type":"object","properties":{"text":{"type":"string","minLength":1,"maxLength":4096,"description":"New text body. Replaces the existing value."},"visibility":{"allOf":[{"$ref":"#/components/schemas/ScratchNoteVisibility"}],"description":"New visibility. May be tightened (CLINIC → PERSONAL) by the original author only."}}},"DeleteScratchNoteInput":{"description":"Soft-delete payload — tags the note `entered-in-error`.","type":"object","required":["reason"],"properties":{"reason":{"type":"string","minLength":1,"description":"Audit-required free-text justification."}}}},"responses":{"Unauthenticated":{"description":"Missing or invalid credentials.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"Forbidden":{"description":"Authenticated but insufficient scopes.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"NotFound":{"description":"Resource not found.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"RateLimited":{"description":"Rate limit exceeded.","headers":{"RateLimit-Limit":{"schema":{"type":"integer"}},"RateLimit-Remaining":{"schema":{"type":"integer"}},"RateLimit-Reset":{"schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"InternalError":{"description":"Internal server error.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"ValidationFailed":{"description":"Validation failed (bad input, missing Idempotency-Key, replay with different payload).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"UpstreamUnavailable":{"description":"Upstream unreachable, circuit breaker open, or service not configured.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"paths":{"/health":{"get":{"operationId":"getHealth","summary":"Liveness and readiness probe","description":"No authentication required. Used by load balancers and uptime monitors. Always answers 200 while the instance is serving; read `status` and `upstreams` in the body to tell a healthy gateway from a degraded one.","security":[],"tags":["Observability"],"responses":{"200":{"description":"The instance is serving. `status` is `ok`, or `degraded` when an upstream breaker is open.","headers":{"X-Correlation-Id":{"$ref":"#/components/headers/X-Correlation-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthStatus"}}}}}}},"/openapi.json":{"get":{"operationId":"getOpenApiSpec","summary":"OpenAPI specification","description":"Returns this document as JSON. No authentication required.","security":[],"tags":["Observability"],"responses":{"200":{"description":"OpenAPI specification JSON","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/graphql":{"servers":[{"url":"https://api.profilehealth.com","description":"Production (root, no /v1 prefix — GraphQL is unversioned per ADR-0003)"},{"url":"https://api-staging.profilehealth.com","description":"Staging"},{"url":"http://localhost:3001","description":"Local development"}],"post":{"operationId":"graphqlExecute","summary":"Execute a GraphQL operation","description":"Canonical GraphQL endpoint (ADR-0001). Accepts queries, mutations,\nand subscriptions per the GraphQL-over-HTTP spec.\n\nUse this when your client speaks GraphQL natively. For partners\nthat prefer REST, equivalent operations are exposed under `/v1/*`\n(see other paths in this document) — they re-enter the same\nplugin chain (auth, rate limit, audit), so behavior is identical.\n\nAuthentication, rate limits, idempotency and audit are enforced\nidentically to the REST façade.\n","tags":["GraphQL"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["query"],"properties":{"query":{"type":"string"},"operationName":{"type":"string"},"variables":{"type":"object","additionalProperties":true}}}}}},"responses":{"200":{"description":"GraphQL response. Always 200 even on operation errors —\ncheck `errors[].extensions.code` against the ADR-0004 enum.\n","headers":{"X-Correlation-Id":{"$ref":"#/components/headers/X-Correlation-Id"}},"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","nullable":true},"errors":{"type":"array","items":{"type":"object"}}}}}}},"405":{"description":"Method Not Allowed — GraphQL accepts POST only. Returned for\n`GET /graphql` in production (ADR-0013 §\"GET /graphql UX fix\",\namended 2026-04-25 to use METHOD_NOT_ALLOWED rather than\nVALIDATION_FAILED). Development environment serves GraphiQL\non GET instead.\n","headers":{"X-Correlation-Id":{"$ref":"#/components/headers/X-Correlation-Id"},"Allow":{"schema":{"type":"string","enum":["POST"]}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"examples":{"method_not_allowed":{"summary":"GET /graphql in production","value":{"type":"https://errors.profilehealth.com/method-not-allowed","title":"METHOD_NOT_ALLOWED","status":405,"detail":"GraphQL endpoint accepts POST only. See /v1/docs for the Quick Start, or use the REST endpoints listed in /v1/openapi.json.","correlationId":"7e8c3a2b-19d4-4f6e-b551-aab1f9c2d3e4"}}}}}}}}},"/intake/invitations":{"post":{"operationId":"createIntakeInvitation","summary":"Issue a patient intake invitation","description":"Issues an onboarding URL the partner can hand to a patient. This is\nthe REST facade for the GraphQL `Mutation.createIntakeInvitation`\n(ADR-0001 / ADR-0013) — internally synthesizes a GraphQL request and\nre-enters the Yoga plugin chain so authentication, rate limits, and\naudit emission run identically to a direct GraphQL call.\n\nRequired scope: `intake:write`. The `clinicId` must be in the\npartner's `allowedClinicIds` (set by a system administrator in\n`/admin/partner-api`). The `Idempotency-Key` header is required and\nscoped to the partner: a retry with the same key returns the\noriginal token rather than creating a duplicate.\n","tags":["Intake"],"parameters":[{"in":"header","name":"Idempotency-Key","required":true,"schema":{"type":"string","maxLength":128},"description":"Unique per logical operation. Replays return the original record."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntakeInvitationInput"},"examples":{"minimal":{"summary":"Minimum required fields","value":{"email":"patient@example.com","name":"Ada Lovelace","clinicId":"0991c6ab-aea2-486b-8b79-a385b51eddf0"}},"full":{"summary":"All optional fields supplied","value":{"email":"patient@example.com","firstName":"Ada","lastName":"Lovelace","phone":"+15551234567","clinicId":"0991c6ab-aea2-486b-8b79-a385b51eddf0","expiresInDays":14,"useMockData":false}}}}}},"responses":{"201":{"description":"Invitation created. A retry with the same `Idempotency-Key`\nand the same payload returns the original invitation with\n`Idempotency-Replay: true` on the response — partners can\nbranch on that header to skip work already performed on the\noriginal call.\n","headers":{"X-Correlation-Id":{"$ref":"#/components/headers/X-Correlation-Id"},"Idempotency-Replay":{"$ref":"#/components/headers/Idempotency-Replay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntakeInvitation"},"examples":{"pending":{"summary":"Newly issued invitation","value":{"id":"token_1714065600000_a1b2c3d4e5f6","tokenString":"7K9z8mNpQ4xYvR2sT5uW1eD3aB6cF8hJ-LpO0nM_qZx","url":"https://app.profilehealth.com/patient/onboarding?token=7K9z8mNpQ4xYvR2sT5uW1eD3aB6cF8hJ-LpO0nM_qZx","expiresAt":"2026-05-02T14:30:00.000Z","email":"patient@example.com","name":"Ada Lovelace","clinicId":"0991c6ab-aea2-486b-8b79-a385b51eddf0","createdAt":"2026-04-25T14:30:00.000Z","status":"pending"}}}}}},"401":{"description":"Missing, malformed, or invalid Bearer token","headers":{"X-Correlation-Id":{"$ref":"#/components/headers/X-Correlation-Id"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"examples":{"missing_authorization":{"summary":"No Authorization header","value":{"type":"https://errors.profilehealth.com/unauthenticated","title":"UNAUTHENTICATED","status":401,"detail":"Authentication required to access Mutation.createIntakeInvitation","correlationId":"7e8c3a2b-19d4-4f6e-b551-aab1f9c2d3e4"}},"bad_credentials":{"summary":"Bearer present but key/secret invalid","value":{"type":"https://errors.profilehealth.com/unauthenticated","title":"UNAUTHENTICATED","status":401,"detail":"Invalid credentials","correlationId":"7e8c3a2b-19d4-4f6e-b551-aab1f9c2d3e4"}}}}}},"403":{"description":"Authenticated but not allowed (missing scope or clinic not in allowedClinicIds; KNO2 attempt)","headers":{"X-Correlation-Id":{"$ref":"#/components/headers/X-Correlation-Id"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"examples":{"clinic_not_owned":{"summary":"clinicId is not in the partner's allowedClinicIds","value":{"type":"https://errors.profilehealth.com/forbidden","title":"FORBIDDEN","status":403,"detail":"Partner is not authorised for clinicId 0991c6ab-aea2-486b-8b79-a385b51eddf0","correlationId":"7e8c3a2b-19d4-4f6e-b551-aab1f9c2d3e4"}},"kno2_admin_only":{"summary":"kno2Requested rejected (admin-only flow)","value":{"type":"https://errors.profilehealth.com/forbidden","title":"FORBIDDEN","status":403,"detail":"KNO2 doctor-initiated flow is admin-only","correlationId":"7e8c3a2b-19d4-4f6e-b551-aab1f9c2d3e4"}},"missing_scope":{"summary":"API key lacks intake:write","value":{"type":"https://errors.profilehealth.com/forbidden","title":"FORBIDDEN","status":403,"detail":"Scope intake:write is required to access Mutation.createIntakeInvitation","correlationId":"7e8c3a2b-19d4-4f6e-b551-aab1f9c2d3e4"}}}}}},"422":{"description":"Validation failed (bad input, missing Idempotency-Key, idempotency key reused with a different payload)","headers":{"X-Correlation-Id":{"$ref":"#/components/headers/X-Correlation-Id"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"examples":{"missing_idempotency_key":{"summary":"Idempotency-Key header not supplied","value":{"type":"https://errors.profilehealth.com/validation-failed","title":"VALIDATION_FAILED","status":422,"detail":"Idempotency-Key header is required for this operation","correlationId":"7e8c3a2b-19d4-4f6e-b551-aab1f9c2d3e4"}},"bad_email":{"summary":"Email failed format validation","value":{"type":"https://errors.profilehealth.com/validation-failed","title":"VALIDATION_FAILED","status":422,"detail":"valid email is required","correlationId":"7e8c3a2b-19d4-4f6e-b551-aab1f9c2d3e4"}},"idempotency_key_reused":{"summary":"Same Idempotency-Key seen with a different payload","value":{"type":"https://errors.profilehealth.com/idempotency-key-reused","title":"IDEMPOTENCY_KEY_REUSED","status":422,"detail":"Idempotency key already used for apiKey pk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","correlationId":"7e8c3a2b-19d4-4f6e-b551-aab1f9c2d3e4"}}}}}},"429":{"description":"Per-partner rate limit exceeded","headers":{"X-Correlation-Id":{"$ref":"#/components/headers/X-Correlation-Id"},"RateLimit-Limit":{"schema":{"type":"integer"},"description":"The partner's per-second quota"},"RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Requests left in the current window"},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix timestamp when the window resets"},"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"examples":{"quota_exceeded":{"summary":"Per-second quota exceeded","value":{"type":"https://errors.profilehealth.com/rate-limited","title":"RATE_LIMITED","status":429,"detail":"Rate limit exceeded for partner par_001 (50 req/s)","correlationId":"7e8c3a2b-19d4-4f6e-b551-aab1f9c2d3e4"}}}}}},"503":{"description":"Upstream unavailable (gateway service not configured, partner-intake Function down, or circuit breaker open)","headers":{"X-Correlation-Id":{"$ref":"#/components/headers/X-Correlation-Id"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"examples":{"service_not_configured":{"summary":"Gateway lacks INTAKE_TOKEN_URL / GATEWAY_INTAKE_WORKER_KEY env\n(Phase 4 ops not run yet — see runbook).\n","value":{"type":"https://errors.profilehealth.com/upstream-unavailable","title":"UPSTREAM_UNAVAILABLE","status":503,"detail":"IntakeService not configured","correlationId":"7e8c3a2b-19d4-4f6e-b551-aab1f9c2d3e4"}},"upstream_down":{"summary":"Cloud Function returned 5xx or network error","value":{"type":"https://errors.profilehealth.com/upstream-unavailable","title":"UPSTREAM_UNAVAILABLE","status":503,"detail":"partnerIntakeToken unreachable","correlationId":"7e8c3a2b-19d4-4f6e-b551-aab1f9c2d3e4"}}}}}}}}},"/encounters":{"post":{"operationId":"createEncounter","summary":"Create an Encounter","description":"Forwards to GraphQL `Mutation.createEncounter`. Required scope:\n`encounter:write`. The patientId must reference a Patient in the\npartner's `allowedClinicIds`. Idempotent on the supplied key for\n24h.\n","tags":["Encounters"],"parameters":[{"in":"header","name":"Idempotency-Key","required":true,"description":"Unique-per-logical-operation key (≤128 chars). Replays with the\nsame key + same payload return the original response; replays\nwith a different payload return 422 IDEMPOTENCY_KEY_REUSED.\nScoped to the partner; reuse across partners is fine.\n","schema":{"type":"string","maxLength":128},"example":"order-2026-04-30-7e8c3a2b"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEncounterInput"},"examples":{"minimal":{"summary":"Patient-only — server fills status/class/type/period defaults","value":{"patientId":"0991c6ab-aea2-486b-8b79-a385b51eddf0"}},"ambulatory_consult":{"summary":"Ambulatory consultation with explicit timing","value":{"patientId":"0991c6ab-aea2-486b-8b79-a385b51eddf0","status":"IN_PROGRESS","classCode":"AMB","classSystem":"http://terminology.hl7.org/CodeSystem/v3-ActCode","classDisplay":"ambulatory","type":[{"coding":[{"system":"http://loinc.org","code":"LP173421-1","display":"Consultation"}]}],"periodStart":"2026-04-30T14:30:00Z","participantPractitionerIds":["5b9e3a17-8c12-49d0-aa6c-1f3b8e2d9f44"]}}}}}},"responses":{"201":{"description":"Encounter created","headers":{"X-Correlation-Id":{"$ref":"#/components/headers/X-Correlation-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Encounter"},"examples":{"ambulatory_in_progress":{"summary":"Just-created ambulatory encounter","value":{"id":"7c1f9b3d-2a4e-4d8b-9012-aa6e3f8d1c22","status":"IN_PROGRESS","class":{"system":"http://terminology.hl7.org/CodeSystem/v3-ActCode","code":"AMB","display":"ambulatory"},"type":[{"coding":[{"system":"http://loinc.org","code":"LP173421-1","display":"Consultation"}]}],"subject":{"reference":"Patient/0991c6ab-aea2-486b-8b79-a385b51eddf0"},"period":{"start":"2026-04-30T14:30:00Z"},"versionId":"1","lastUpdated":"2026-04-30T14:30:01.234Z"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/ValidationFailed"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/UpstreamUnavailable"}}}},"/encounters/{id}":{"parameters":[{"in":"path","name":"id","required":true,"description":"Encounter resource id (Medplum UUID, NOT the FHIR `Encounter/{id}` reference).","schema":{"type":"string"},"example":"7c1f9b3d-2a4e-4d8b-9012-aa6e3f8d1c22"}],"get":{"operationId":"getEncounter","summary":"Read an Encounter by id","description":"Forwards to GraphQL `Query.encounter`. Required scope:\n`encounter:read`. Returns 404 when the encounter does not exist\nOR when the patient is not in the caller's allowed clinics\n(anti-enumeration).\n","tags":["Encounters"],"responses":{"200":{"description":"Encounter","headers":{"X-Correlation-Id":{"$ref":"#/components/headers/X-Correlation-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Encounter"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/UpstreamUnavailable"}}},"patch":{"operationId":"updateEncounter","summary":"Update an Encounter","description":"Forwards to GraphQL `Mutation.updateEncounter`. Required scope:\n`encounter:write`. Status transitions follow the ADR-0017 graph;\nre-opening a finished encounter is rejected. Pass `ifMatch` for\noptimistic concurrency.\n","tags":["Encounters"],"parameters":[{"in":"header","name":"Idempotency-Key","required":true,"description":"Unique-per-logical-operation key (≤128 chars). Replays with the\nsame key + same payload return the original response; replays\nwith a different payload return 422 IDEMPOTENCY_KEY_REUSED.\nScoped to the partner; reuse across partners is fine.\n","schema":{"type":"string","maxLength":128},"example":"order-2026-04-30-7e8c3a2b"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateEncounterInput"}}}},"responses":{"200":{"description":"Encounter updated","headers":{"X-Correlation-Id":{"$ref":"#/components/headers/X-Correlation-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Encounter"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationFailed"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/UpstreamUnavailable"}}},"delete":{"operationId":"deleteEncounter","summary":"Soft-delete an Encounter","description":"Forwards to GraphQL `Mutation.deleteEncounter`. Sets\n`status=CANCELLED` and appends the audit reason to `reasonCode`.\nFinished encounters cannot be cancelled — amend the linked note\ninstead. Required scope: `encounter:write`.\n","tags":["Encounters"],"parameters":[{"in":"header","name":"Idempotency-Key","required":true,"description":"Unique-per-logical-operation key (≤128 chars). Replays with the\nsame key + same payload return the original response; replays\nwith a different payload return 422 IDEMPOTENCY_KEY_REUSED.\nScoped to the partner; reuse across partners is fine.\n","schema":{"type":"string","maxLength":128},"example":"order-2026-04-30-7e8c3a2b"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteEncounterInput"}}}},"responses":{"200":{"description":"Encounter cancelled (soft-deleted)","headers":{"X-Correlation-Id":{"$ref":"#/components/headers/X-Correlation-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Encounter"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationFailed"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/UpstreamUnavailable"}}}},"/patients/{patientId}/encounters":{"parameters":[{"in":"path","name":"patientId","required":true,"description":"Patient resource id (resource id only, NOT the full FHIR `Patient/{id}` reference). Must be in the caller's allowed clinics — otherwise the endpoint returns an empty connection (anti-enumeration).","schema":{"type":"string"},"example":"0991c6ab-aea2-486b-8b79-a385b51eddf0"}],"get":{"operationId":"listEncounters","summary":"List Encounters for a patient","description":"Forwards to GraphQL `Query.encounters`. Required scope:\n`encounter:read`. Returns an empty connection when the patient\nis not in any of the caller's allowed clinics.\n","tags":["Encounters"],"parameters":[{"in":"query","name":"status","description":"FHIR Encounter status (lowercase): planned, in-progress, finished, cancelled, ...","schema":{"type":"string","enum":["planned","arrived","triaged","in-progress","onleave","finished","cancelled","entered-in-error","unknown"]}},{"in":"query","name":"type","description":"FHIR token search on Encounter.type, e.g. a LOINC code","schema":{"type":"string"}},{"in":"query","name":"first","description":"Page size (1..100, default 25)","schema":{"type":"integer","minimum":1,"maximum":100}},{"in":"query","name":"after","description":"Opaque cursor from a prior pageInfo.endCursor","schema":{"type":"string"}}],"responses":{"200":{"description":"Encounter connection","headers":{"X-Correlation-Id":{"$ref":"#/components/headers/X-Correlation-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EncounterConnection"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/UpstreamUnavailable"}}}},"/compositions":{"post":{"operationId":"createComposition","summary":"Create a SOAP note (Composition)","description":"Forwards to GraphQL `Mutation.createComposition`. Required scope:\n`note:write`. The patientId must be in the partner's\n`allowedClinicIds`; the encounterId must reference a non-cancelled\nEncounter for the same patient. Status starts at PRELIMINARY.\n","tags":["Compositions"],"parameters":[{"in":"header","name":"Idempotency-Key","required":true,"description":"Unique-per-logical-operation key (≤128 chars). Replays with the\nsame key + same payload return the original response; replays\nwith a different payload return 422 IDEMPOTENCY_KEY_REUSED.\nScoped to the partner; reuse across partners is fine.\n","schema":{"type":"string","maxLength":128},"example":"order-2026-04-30-7e8c3a2b"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCompositionInput"},"examples":{"soap_note":{"summary":"Full SOAP note with all four canonical sections","value":{"patientId":"0991c6ab-aea2-486b-8b79-a385b51eddf0","encounterId":"7c1f9b3d-2a4e-4d8b-9012-aa6e3f8d1c22","title":"Follow-up — hypertension","section":[{"soap":"SUBJECTIVE","narrative":"Patient reports headaches improved on lisinopril 10mg daily."},{"soap":"OBJECTIVE","narrative":"BP 128/82, HR 72, weight stable."},{"soap":"ASSESSMENT","narrative":"Hypertension well controlled on current regimen."},{"soap":"PLAN","narrative":"Continue lisinopril 10mg. Recheck in 3 months."}]}},"minimal_subjective":{"summary":"PRELIMINARY note with only Subjective — sections may be added before signing","value":{"patientId":"0991c6ab-aea2-486b-8b79-a385b51eddf0","encounterId":"7c1f9b3d-2a4e-4d8b-9012-aa6e3f8d1c22","section":[{"soap":"SUBJECTIVE","narrative":"Patient called about lab results."}]}}}}}},"responses":{"201":{"description":"Composition created","headers":{"X-Correlation-Id":{"$ref":"#/components/headers/X-Correlation-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Composition"},"examples":{"preliminary":{"summary":"New PRELIMINARY note immediately after creation","value":{"id":"9f8c2b41-5d3e-47a0-b289-c5e1f4d9a7b3","status":"PRELIMINARY","type":{"coding":[{"system":"http://loinc.org","code":"34117-2","display":"History and physical note"}]},"subject":{"reference":"Patient/0991c6ab-aea2-486b-8b79-a385b51eddf0"},"encounter":{"reference":"Encounter/7c1f9b3d-2a4e-4d8b-9012-aa6e3f8d1c22"},"author":[{"reference":"Practitioner/5b9e3a17-8c12-49d0-aa6c-1f3b8e2d9f44"}],"date":"2026-04-30T14:35:00Z","title":"Follow-up — hypertension","section":[{"title":"Subjective","code":{"coding":[{"system":"http://loinc.org","code":"10164-2","display":"History of present illness Narrative"}]},"text":{"status":"generated","div":"<div xmlns=\"http://www.w3.org/1999/xhtml\">Patient reports headaches improved on lisinopril 10mg daily.</div>"}}],"versionId":"1","lastUpdated":"2026-04-30T14:35:00.456Z"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationFailed"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/UpstreamUnavailable"}}}},"/compositions/{id}":{"parameters":[{"in":"path","name":"id","required":true,"description":"Composition (SOAP note) resource id.","schema":{"type":"string"},"example":"9f8c2b41-5d3e-47a0-b289-c5e1f4d9a7b3"}],"get":{"operationId":"getComposition","summary":"Read a Composition by id","description":"Forwards to GraphQL `Query.composition`. Required scope:\n`note:read`. Returns 404 when the note does not exist OR when\nthe linked patient is not in the caller's allowed clinics.\n","tags":["Compositions"],"responses":{"200":{"description":"Composition","headers":{"X-Correlation-Id":{"$ref":"#/components/headers/X-Correlation-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Composition"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/UpstreamUnavailable"}}},"delete":{"operationId":"deleteComposition","summary":"Soft-delete a Composition","description":"Sets `status=ENTERED_IN_ERROR`. On a FINAL note, `force=true`\nplus a non-empty `reason` is required. Required scope:\n`note:amend`.\n","tags":["Compositions"],"parameters":[{"in":"header","name":"Idempotency-Key","required":true,"description":"Unique-per-logical-operation key (≤128 chars). Replays with the\nsame key + same payload return the original response; replays\nwith a different payload return 422 IDEMPOTENCY_KEY_REUSED.\nScoped to the partner; reuse across partners is fine.\n","schema":{"type":"string","maxLength":128},"example":"order-2026-04-30-7e8c3a2b"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteCompositionInput"}}}},"responses":{"200":{"description":"Composition soft-deleted","headers":{"X-Correlation-Id":{"$ref":"#/components/headers/X-Correlation-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Composition"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationFailed"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/UpstreamUnavailable"}}}},"/compositions/{id}/sections/{section}":{"parameters":[{"in":"path","name":"id","required":true,"description":"Composition (SOAP note) resource id. Must reference a PRELIMINARY note.","schema":{"type":"string"},"example":"9f8c2b41-5d3e-47a0-b289-c5e1f4d9a7b3"},{"in":"path","name":"section","required":true,"description":"Lowercase canonical SOAP section to replace. The narrative for\nthis section is overwritten in place. Custom (non-SOAP)\nsections cannot be addressed via this endpoint — use\n`PATCH /compositions/{id}` instead.\n","schema":{"type":"string","enum":["subjective","objective","assessment","plan"]},"example":"objective"}],"patch":{"operationId":"updateCompositionSection","summary":"Replace a SOAP section's narrative on a draft note","description":"Forwards to GraphQL `Mutation.updateCompositionSection`. Allowed\nonly when the Composition is PRELIMINARY. Optional `ifMatch`\nprovides optimistic concurrency.\n","tags":["Compositions"],"parameters":[{"in":"header","name":"Idempotency-Key","required":true,"description":"Unique-per-logical-operation key (≤128 chars). Replays with the\nsame key + same payload return the original response; replays\nwith a different payload return 422 IDEMPOTENCY_KEY_REUSED.\nScoped to the partner; reuse across partners is fine.\n","schema":{"type":"string","maxLength":128},"example":"order-2026-04-30-7e8c3a2b"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCompositionSectionInput"},"examples":{"objective_replacement":{"summary":"Replace the Objective section narrative","value":{"narrative":"BP 122/80, HR 68. Auscultation clear bilaterally."}},"with_etag":{"summary":"Same update with optimistic concurrency","value":{"narrative":"BP 122/80, HR 68. Auscultation clear bilaterally.","ifMatch":"W/\"3\""}}}}}},"responses":{"200":{"description":"Composition updated","headers":{"X-Correlation-Id":{"$ref":"#/components/headers/X-Correlation-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Composition"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/ValidationFailed"},"422":{"$ref":"#/components/responses/ValidationFailed"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/UpstreamUnavailable"}}}},"/compositions/{id}/sign":{"parameters":[{"in":"path","name":"id","required":true,"description":"Composition resource id. Must reference a PRELIMINARY note with all four canonical SOAP sections present and non-empty.","schema":{"type":"string"},"example":"9f8c2b41-5d3e-47a0-b289-c5e1f4d9a7b3"}],"post":{"operationId":"signComposition","summary":"Sign a draft Composition","description":"Promotes status PRELIMINARY → FINAL, sets `attester[].mode=legal`\nwith the calling Practitioner. Refuses if any of the four\ncanonical SOAP sections is missing or empty. Required scope:\n`note:sign`.\n","tags":["Compositions"],"parameters":[{"in":"header","name":"Idempotency-Key","required":true,"description":"Unique-per-logical-operation key (≤128 chars). Replays with the\nsame key + same payload return the original response; replays\nwith a different payload return 422 IDEMPOTENCY_KEY_REUSED.\nScoped to the partner; reuse across partners is fine.\n","schema":{"type":"string","maxLength":128},"example":"order-2026-04-30-7e8c3a2b"}],"responses":{"200":{"description":"Composition signed","headers":{"X-Correlation-Id":{"$ref":"#/components/headers/X-Correlation-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Composition"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationFailed"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/UpstreamUnavailable"}}}},"/compositions/{id}/amend":{"parameters":[{"in":"path","name":"id","required":true,"description":"Composition resource id. Must reference a FINAL note. Creates a NEW PRELIMINARY composition with `relatesTo[code=replaces]` linking back to this id.","schema":{"type":"string"},"example":"9f8c2b41-5d3e-47a0-b289-c5e1f4d9a7b3"}],"post":{"operationId":"amendComposition","summary":"Amend a finalized Composition","description":"Creates a NEW PRELIMINARY Composition with\n`relatesTo[code=replaces]` to the original. The amendment must\nbe signed separately via `/compositions/{newId}/sign`. Required\nscope: `note:amend`.\n","tags":["Compositions"],"parameters":[{"in":"header","name":"Idempotency-Key","required":true,"description":"Unique-per-logical-operation key (≤128 chars). Replays with the\nsame key + same payload return the original response; replays\nwith a different payload return 422 IDEMPOTENCY_KEY_REUSED.\nScoped to the partner; reuse across partners is fine.\n","schema":{"type":"string","maxLength":128},"example":"order-2026-04-30-7e8c3a2b"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AmendCompositionInput"}}}},"responses":{"201":{"description":"Amendment draft created","headers":{"X-Correlation-Id":{"$ref":"#/components/headers/X-Correlation-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Composition"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationFailed"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/UpstreamUnavailable"}}}},"/patients/{patientId}/compositions":{"parameters":[{"in":"path","name":"patientId","required":true,"description":"Patient resource id. Returns an empty connection when the patient is not in the caller's allowed clinics (anti-enumeration).","schema":{"type":"string"},"example":"0991c6ab-aea2-486b-8b79-a385b51eddf0"}],"get":{"operationId":"listCompositions","summary":"List Compositions for a patient","description":"Forwards to GraphQL `Query.compositions`. Required scope:\n`note:read`. Returns an empty connection when the patient is\nnot in any of the caller's allowed clinics.\n","tags":["Compositions"],"parameters":[{"in":"query","name":"status","description":"Lowercase FHIR Composition status: preliminary | final | amended | entered-in-error","schema":{"type":"string","enum":["preliminary","final","amended","entered-in-error"]}},{"in":"query","name":"encounterId","description":"Filter to compositions linked to a specific Encounter.","schema":{"type":"string"}},{"in":"query","name":"type","description":"LOINC token search on Composition.type (e.g. 34117-2).","schema":{"type":"string"}},{"in":"query","name":"first","description":"Page size. Default 25, max 100.","schema":{"type":"integer","minimum":1,"maximum":100},"example":25},{"in":"query","name":"after","description":"Opaque cursor from a prior `pageInfo.endCursor`. Omit on first page.","schema":{"type":"string"}}],"responses":{"200":{"description":"Composition connection","headers":{"X-Correlation-Id":{"$ref":"#/components/headers/X-Correlation-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompositionConnection"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/UpstreamUnavailable"}}}},"/scratch-notes":{"post":{"operationId":"createScratchNote","summary":"Create a scratch / sticky note","description":"Forwards to GraphQL `Mutation.createScratchNote`. Required scope:\n`scratch-note:write`. Author is the calling Practitioner. Visibility\ndefaults to CLINIC; PERSONAL is reserved for internal principals.\n","tags":["ScratchNotes"],"parameters":[{"in":"header","name":"Idempotency-Key","required":true,"description":"Unique-per-logical-operation key (≤128 chars). Replays with the\nsame key + same payload return the original response; replays\nwith a different payload return 422 IDEMPOTENCY_KEY_REUSED.\nScoped to the partner; reuse across partners is fine.\n","schema":{"type":"string","maxLength":128},"example":"order-2026-04-30-7e8c3a2b"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateScratchNoteInput"},"examples":{"clinic_visible":{"summary":"Default visibility — readable by any clinic-allowed clinician","value":{"patientId":"0991c6ab-aea2-486b-8b79-a385b51eddf0","text":"Patient prefers afternoon appointments. Allergic to latex."}},"personal":{"summary":"Author-only sticky note (internal principals only)","value":{"patientId":"0991c6ab-aea2-486b-8b79-a385b51eddf0","text":"Reminder to follow up on imaging order tomorrow.","visibility":"PERSONAL"}}}}}},"responses":{"201":{"description":"Scratch note created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScratchNote"},"examples":{"clinic_visible":{"summary":"Newly created CLINIC-visibility scratch note","value":{"id":"ab12cd34-ef56-7890-abcd-1234567890ef","subject":{"reference":"Patient/0991c6ab-aea2-486b-8b79-a385b51eddf0"},"author":{"reference":"Practitioner/5b9e3a17-8c12-49d0-aa6c-1f3b8e2d9f44"},"text":"Patient prefers afternoon appointments. Allergic to latex.","visibility":"CLINIC","createdAt":"2026-04-30T14:40:00Z","versionId":"1","deleted":false}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/ValidationFailed"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/UpstreamUnavailable"}}}},"/scratch-notes/{id}":{"parameters":[{"in":"path","name":"id","required":true,"description":"ScratchNote (FHIR Basic) resource id.","schema":{"type":"string"},"example":"ab12cd34-ef56-7890-abcd-1234567890ef"}],"get":{"operationId":"getScratchNote","summary":"Read a scratch note by id","description":"Required scope: `scratch-note:read`. PERSONAL-visibility notes\nare visible only to the original author; cross-author requests\nreturn 404 unless the caller holds `admin:scratch-notes:read`.\n","tags":["ScratchNotes"],"responses":{"200":{"description":"Scratch note","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScratchNote"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/UpstreamUnavailable"}}},"patch":{"operationId":"updateScratchNote","summary":"Update a scratch note","description":"Subject and author are immutable. Only the original author may\nedit. Required scope: `scratch-note:write`.\n","tags":["ScratchNotes"],"parameters":[{"in":"header","name":"Idempotency-Key","required":true,"description":"Unique-per-logical-operation key (≤128 chars). Replays with the\nsame key + same payload return the original response; replays\nwith a different payload return 422 IDEMPOTENCY_KEY_REUSED.\nScoped to the partner; reuse across partners is fine.\n","schema":{"type":"string","maxLength":128},"example":"order-2026-04-30-7e8c3a2b"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateScratchNoteInput"}}}},"responses":{"200":{"description":"Scratch note updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScratchNote"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationFailed"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/UpstreamUnavailable"}}},"delete":{"operationId":"deleteScratchNote","summary":"Soft-delete a scratch note","description":"Adds the `entered-in-error` tag. Idempotent on already-deleted\nnotes. Audit reason required. Required scope: `scratch-note:write`.\n","tags":["ScratchNotes"],"parameters":[{"in":"header","name":"Idempotency-Key","required":true,"description":"Unique-per-logical-operation key (≤128 chars). Replays with the\nsame key + same payload return the original response; replays\nwith a different payload return 422 IDEMPOTENCY_KEY_REUSED.\nScoped to the partner; reuse across partners is fine.\n","schema":{"type":"string","maxLength":128},"example":"order-2026-04-30-7e8c3a2b"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteScratchNoteInput"}}}},"responses":{"200":{"description":"Scratch note soft-deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScratchNote"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationFailed"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/UpstreamUnavailable"}}}},"/patients/{patientId}/scratch-notes":{"parameters":[{"in":"path","name":"patientId","required":true,"description":"Patient resource id. Returns an empty connection when the patient is not in the caller's allowed clinics (anti-enumeration).","schema":{"type":"string"},"example":"0991c6ab-aea2-486b-8b79-a385b51eddf0"}],"get":{"operationId":"listScratchNotes","summary":"List scratch notes for a patient","description":"Required scope: `scratch-note:read`. The service filters PERSONAL\nnotes belonging to other authors before returning. Soft-deleted\nnotes (entered-in-error tag) are dropped.\n","tags":["ScratchNotes"],"parameters":[{"in":"query","name":"visibility","schema":{"type":"string","enum":["personal","clinic"]}},{"in":"query","name":"authorId","schema":{"type":"string"}},{"in":"query","name":"first","description":"Page size. Default 25, max 100.","schema":{"type":"integer","minimum":1,"maximum":100},"example":25},{"in":"query","name":"after","description":"Opaque cursor from a prior `pageInfo.endCursor`. Omit on first page.","schema":{"type":"string"}}],"responses":{"200":{"description":"Scratch note connection","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScratchNoteConnection"}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/UpstreamUnavailable"}}}}}}