# Docs - Get started - [Get started](/get-started): Learn what Ovok does, create your first app, and pick the path that fits what you're building. - [Create your first Ovok app](/get-started/quickstart): Scaffold a web and mobile workspace connected to the Ovok sandbox, sign in, and list your project's patients. - **Start building** - [Set up your project](/authentication/project-setup) - [FHIR Basics](/fhir-basics): Learn the FHIR concepts used to represent and connect healthcare data in Ovok. - Example RPM Apps - [Example RPM repositories](/example-rpm-app): Choose a public Ovok RPM example repository: patient mobile app, clinician dashboard, clinic EHR, or the monorepo that runs them together. - rpm-web-dashboard - [rpm-web-dashboard](/example-rpm-app/web-dashboard): Start and explore the public Ovok clinician dashboard example built with React, Vite, TypeScript, and the Ovok SDK. - **Get started** - [Run rpm-web-dashboard](/example-rpm-app/web-dashboard/getting-started): Clone and run the synthetic clinician dashboard locally. - [Connect rpm-web-dashboard to a sandbox](/example-rpm-app/web-dashboard/sandbox-setup): Configure a local dashboard build for one Ovok sandbox project and practitioner account. - **Explore the code** - [Explore the dashboard app shell](/example-rpm-app/web-dashboard/app-shell): Trace page selection, workspace mode, patient selection, and the main clinician dashboard shell. - [Follow patient data through the dashboard](/example-rpm-app/web-dashboard/patient-data): Trace patient, measurement, and questionnaire reads from the Ovok SDK into the clinician review UI. - [Explore Signals and support in the dashboard](/example-rpm-app/web-dashboard/signals-and-support): See how the dashboard reads project Signals status and alerts through Ovok and keeps the screen read-only. - [Dashboard demo data and tests](/example-rpm-app/web-dashboard/demo-and-tests): Understand the synthetic clinician workspace and the focused checks for dashboard data mapping. - rpm-ehr-example-app - [rpm-ehr-example-app](/example-rpm-app/ehr): Run and explore the public Ovok EHR example for clinic intake, CHF RPM enrollment, patient charts, and FHIR workflows. - **Get started** - [Run rpm-ehr-example-app](/example-rpm-app/ehr/getting-started): Clone and run the synthetic Ovok clinic EHR example locally. - [Connect rpm-ehr-example-app to an Ovok sandbox](/example-rpm-app/ehr/sandbox-setup): Configure the EHR example for an authorized Ovok sandbox project and Practitioner account. - **Explore the code** - [Clinic workflow and FHIR records in the EHR example](/example-rpm-app/ehr/clinic-workflow): Follow the EHR example's patient intake, Practitioner authorization, FHIR reads and writes, Signals view, and synthetic relay boundaries. - **rpm-mobile-app** - [rpm-mobile-app](/example-rpm-app/mobile-app): Start and explore Ovok's public patient-facing RPM example built with Expo, React Native, and the Ovok SDK. - **Get started** - [Get started with the example RPM app](/example-rpm-app/getting-started): Clone Ovok Care, install its dependencies, and run its synthetic-data demo in an iOS simulator. - [Connect the RPM app to a sandbox project](/example-rpm-app/sandbox-setup): Configure Ovok Care for an authorized sandbox Patient account without placing credentials in the mobile app. - Explore the app - [RPM app Home tab](/example-rpm-app/app-tabs/home): Explore the daily measurement and check-in workflow in Ovok Care. - [RPM app Diary tab](/example-rpm-app/app-tabs/diary): Understand the local reading and check-in history shown by the RPM example app. - [RPM app Support tab](/example-rpm-app/app-tabs/support): Find device instructions and troubleshooting in the Ovok Care example. - [RPM app Settings tab](/example-rpm-app/app-tabs/settings): Explore appearance, device management, and Patient sign-in in the RPM example app. - Explore the code - [Patient authentication in the RPM app](/example-rpm-app/integrations/authentication): See how the example configures tenant context and signs in an existing Patient account. - [FHIR persistence in the RPM app](/example-rpm-app/integrations/fhir-persistence): Follow the example's Patient-scoped QuestionnaireResponse and measurement writes through Ovok. - [Bluetooth measurements in the RPM app](/example-rpm-app/integrations/bluetooth): Trace discovery, pairing, normalized device results, and save handoff in Ovok Care. - [Local data and sync in the RPM app](/example-rpm-app/integrations/local-data-and-sync): Understand demo isolation, local persistence, foreground sync, and retry states in Ovok Care. - rpm-example-app - [rpm-example-app](/example-rpm-app/monorepo): Run the patient mobile app, clinician dashboard, and clinic EHR together from Ovok's public Turborepo example monorepo. - **Get started** - [Run the RPM example apps](/example-rpm-app/monorepo/getting-started): Install the Ovok RPM example monorepo and start its mobile and web workspaces. - **Explore the code** - [Workspaces and commands in rpm-example-app](/example-rpm-app/monorepo/workspaces): Understand the monorepo's npm workspaces, app boundaries, and Turborepo commands. - [Explore the mobile app in the monorepo](/example-rpm-app/monorepo/mobile-app): Find the mobile app entry point, patient screens, device integration, and persistence in rpm-example-app. - [Explore the dashboard in the monorepo](/example-rpm-app/monorepo/web-dashboard): Find the dashboard entry point, patient review screens, data mapper, and read-only Signals view in rpm-example-app. - [Follow the RPM example data flow](/example-rpm-app/monorepo/data-flow): Trace a patient reading from the mobile app into Ovok and into the clinician dashboard review. - [Build with AI agents](/working-with-ai-agents) - **Choose your SDK** - [JavaScript SDK](/web-sdk) - [React Native SDK](/native-sdk) - [Java SDK](/java-sdk) - [Python SDK](/python-sdk) - [Flutter SDK](/flutter-sdk) - **Follow a guide** - [Blood-pressure monitoring](/guides/chf-remote-monitoring) - [Medication reminders](/guides/medication-reminder) - [Type 2 diabetes care](/guides/type-2-diabetes) - [Whole-person wellness](/guides/whole-person-wellness) - Ovok - **Authentication** - [Authentication](/authentication): Sign patients and practitioners in to an Ovok project with tenant authentication, and learn which project settings control it. - [Set up your project](/authentication/project-setup): Configure the project settings and AccessPolicies that patient and practitioner sign-in and registration depend on, then check that it works. - [Patient sign-in](/authentication/patient-login): Sign a patient in to one tenant with the three-step PKCE flow, and the setting that controls it. - [Practitioner sign-in](/authentication/practitioner-login): Sign a practitioner in, choose which tenant to enter, and the setting that controls it. - [Patient registration](/authentication/patient-registration): Let a patient create their own account in a tenant, the project settings it needs, and business-email sign-up. - [Practitioner registration](/authentication/practitioner-registration): Let a practitioner create their own account in a tenant, why it is off by default, and what the project must configure first. - [Carers](/authentication/carers): How a carer (a FHIR RelatedPerson) gets an Ovok account, signs in, resets a password, and what a carer can and cannot do. - Your account - [Get account information](/authentication/account/get-account-information): Read who an access token belongs to with GET /auth/me, including the project, profile, access policy and sessions. - [List sessions](/authentication/account/list-sessions): List the signed-in user's sessions with GET /auth/session to build a "where you are signed in" screen. - [Revoke sessions](/authentication/account/revoke-sessions): Sign the caller out of the current session, all other sessions, all sessions, or one session id with DELETE /auth/session/{option}. - [Refresh an access token](/authentication/account/refresh-token): Exchange a refresh token for a new access token and a new refresh token with POST /auth/refresh-token. - [Log out](/authentication/account/log-out): End an access token on the server with POST /v1/auth/logout so it stops working before it expires. - [Change password](/authentication/account/change-password): Change the password of the signed-in user with PATCH /v2/auth/change-password, using the current password as proof. - [Send a password reset email](/authentication/account/reset-password): Email a patient, carer or practitioner a password reset link with POST /v2/auth/reset-password, without revealing whether the account exists. - [Set a new password from a reset email](/authentication/account/process-password-reset): Set a new password with the id and secret from a reset or invitation email using POST /v2/auth/reset-password/process. - [Schedule account deletion](/authentication/account/delete-account): Schedule the deletion of the signed-in user's own account with DELETE /auth/delete, and how signing in again cancels it. - [Get medical consent](/authentication/account/get-medical-consent): Read the signed-in practitioner's medical settings notifications consent with GET /v1/me/consent/medical, or null when it is not given. - [Give or withdraw the medical consent](/authentication/account/update-medical-consent): Give or withdraw the medical settings notifications consent of the signed-in practitioner with POST /v1/me/consent/medical. - [Get legal consent](/authentication/account/get-legal-consent): Read which terms the signed-in practitioner accepted, when, and whether the acceptance is still active, with GET /v1/me/consent/legal. - [Accept or withdraw the terms](/authentication/account/update-legal-consent): Accept or withdraw the terms for the signed-in practitioner with POST /v1/me/consent/legal and get the resulting legal consent back. - [Errors and troubleshooting](/authentication/troubleshooting): The error format of the tenant authentication routes, every message you can meet, and what to check and change when sign-in or registration does not behave as expected. - **Access policies** - [Access policies](/access-policies): Learn how Ovok AccessPolicies shape project membership access, how default policies are assigned, and how Signals routes use custom policy rows. - [AccessPolicy model and membership](/access-policies/policy-model): Understand AccessPolicy resource rules, interactions, criteria, and how a project membership receives policies. - [Default AccessPolicies](/access-policies/defaults): Learn where Ovok gets the default AccessPolicy for a new patient or practitioner, which routes use it, and what defaults do not do. - [Signals route policies](/access-policies/signals): The signals_* AccessPolicy rows that authorize Signals routes through Ovok, what each one opens, and which app types hold it. - **Projects** - [Projects](/projects): Understand projects and sub-projects on Ovok, and choose between the routes that list the projects you administer and create or delete sub-projects. - [List administered projects](/projects/list-projects): List every project you administer, with the tenant code you need to enter it, to build a project picker. - [List sub-projects](/projects/list-sub-projects): Read where your current project sits in the hierarchy, with its parent and its direct sub-projects. - [Create a sub-project](/projects/create-sub-project): Create a sub-project under your current project: what is created, who owns it, and what the response contains for a user or a client application. - [Delete a sub-project](/projects/delete-sub-project): Delete an empty direct sub-project of your project together with the setup Ovok created for it, and how to retry a delete that ended in a 502. - **Invitations** - [Invitations](/invitations): Bring people into an Ovok project with invitations, and choose between adding a team member, sharing a patient's record with a practitioner or a carer, and inviting a patient into care. - [Invite a team member](/invitations/team-members): Add a practitioner to your project with POST /v1/projects/me/members, the required fields, access policies, and the cases that surprise people. - [Share a record with a practitioner](/invitations/share-with-practitioner): Let a signed-in patient share their whole record with a practitioner by email, and how the practitioner accepts it. - [Share readings with a carer](/invitations/share-with-a-carer): Let a signed-in patient share chosen readings with a carer by email, how the carer accepts and gets an account, and what the carer can then read. - [Invite a patient into care](/invitations/invite-a-patient): Let a practitioner invite a patient by email and see their record, and how the patient accepts it. - **Settings and features** - [Settings and features](/settings-and-features): Understand project features, settings, defaults, and the APIs that control them in Ovok. - Features - [bots](/settings-and-features/features/bots): Run, deploy and subscribe to Bots in a project, and which other features depend on it. - [cron](/settings-and-features/features/cron): Run Bots on a schedule, and why turning the feature on or off does not change schedules that already exist. - [email](/settings-and-features/features/email): Let Bots send email, and what this feature does not control, including the emails Ovok sends itself. - [websocket-subscriptions](/settings-and-features/features/websocket-subscriptions): Receive live updates over a WebSocket Subscription channel, what the feature gates, and what it does not. - [transaction-bundles](/settings-and-features/features/transaction-bundles): Write several resources all-or-nothing with a FHIR transaction Bundle, and what happens silently when the feature is off. - [ai](/settings-and-features/features/ai): Call a language model through the API with your own key, what leaves your project when you do, and what the feature does not cover. - [aws-comprehend](/settings-and-features/features/aws-comprehend): What the aws-comprehend flag does today, and where Comprehend Medical analysis actually comes from. - [aws-textract](/settings-and-features/features/aws-textract): Extract text from a stored document with AWS Textract, what it needs and costs, and how it relates to aws-comprehend. - [google-auth-required](/settings-and-features/features/google-auth-required): Require Google as the only sign-in method, which sign-in paths enforce it, and how to avoid locking yourself out. - [graphql-introspection](/settings-and-features/features/graphql-introspection): The graphql-introspection flag has no effect today. Introspection is controlled platform-wide and works in the sandbox with the flag off. - [validate-terminology](/settings-and-features/features/validate-terminology): Reject coded values that do not satisfy a required terminology binding when a resource is written, and what this feature does not affect. - Settings - [PATIENT_LOGIN_ENABLED](/settings-and-features/settings/patient-login-enabled): Pause or resume patient sign-in for a project without affecting practitioners. - [PRACTITIONER_LOGIN_ENABLED](/settings-and-features/settings/practitioner-login-enabled): Pause or resume practitioner sign-in for a project, and what the switch does and does not cover. - [PATIENT_INVITATION_ENABLED](/settings-and-features/settings/patient-invitation-enabled): Allow or stop patient invitations, and what an invitation needs before it can be sent. - [PRACTITIONER_INVITATION_ENABLED](/settings-and-features/settings/practitioner-invitation-enabled): Allow or stop practitioner invitations, which routes it covers, and which access policy an invited practitioner gets. - [RELATED_PERSON_LOGIN_ENABLED](/settings-and-features/settings/related-person-login-enabled): Pause or resume carer (RelatedPerson) sign-in for a project without affecting patients or practitioners. - [RELATED_PERSON_INVITATION_ENABLED](/settings-and-features/settings/related-person-invitation-enabled): Allow or stop patients inviting carers (RelatedPerson), for both sending and accepting an invite. - [PATIENT_REGISTRATION_ENABLED](/settings-and-features/settings/patient-registration-enabled): Let patients create their own account, what the project must have in place first, and the sign-up paths the switch does not cover. - [PRACTITIONER_REGISTRATION_ENABLED](/settings-and-features/settings/practitioner-registration-enabled): Let practitioners create their own account in a project, which access policy they get, and why this is off by default. - [DEFAULT_PRACTITIONER_ACCESS_POLICY](/settings-and-features/settings/default-practitioner-access-policy): The AccessPolicy Ovok gives to new practitioners, which routes read it, and what happens when it is missing or invalid. - [CLINICIAN_INVITE_ON_BUSINESS_EMAIL](/settings-and-features/settings/clinician-invite-on-business-email): Turn a patient registration with a work email into a practitioner invitation, what it needs, and when it silently falls back to a patient account. - [PATIENT_APP_URL](/settings-and-features/settings/patient-app-url): The address Ovok puts in links it emails to patients, how it is resolved, and what is accepted. - [PRACTITIONER_APP_URL](/settings-and-features/settings/practitioner-app-url): The address Ovok puts in links it emails to practitioners, which routes read it, and the routes that do not. - [PATIENT_SHARING_ENABLED](/settings-and-features/settings/patient-sharing-enabled): Let patients share chosen readings with another patient or a carer, how to turn it on, and why it is not in the project settings record. - [MAILING_ENABLED](/settings-and-features/settings/mailing-enabled): A reserved project setting. It is accepted and stored, but no Ovok route reads it today; here is what controls email instead. - [CONTENT_ENABLED](/settings-and-features/settings/content-enabled): A marker the Ovok Console sets when you enable the CMS. It is stored, but no Ovok route reads it. - **Files and documents** - [Files and documents](/files-and-documents): Store, list, download, replace, update and delete files with the document routes, and choose between the private and public download. - [Create document](/files-and-documents/create-document): Create a document and get a signed form to upload its file straight to file storage. - [Replace document file](/files-and-documents/replace-document): Replace the file of an existing document: update its metadata and get a new signed upload form. - [Search documents](/files-and-documents/search-documents): List the documents you can read, newest first, with paging and sorting. - [Download document](/files-and-documents/get-document): Download a document's file by redirect to a short-lived signed URL, with optional image resizing. - [Download public document](/files-and-documents/get-public-document): Download the file of a public document without a bearer token, using its public token. - [Update document metadata](/files-and-documents/update-document): Change a document's file name, content type or public status without uploading a new file. - [Delete document](/files-and-documents/delete-document): Delete a document and its stored file. This cannot be undone. - [Upload binary file](/files-and-documents/upload-binary): Deprecated. Upload one file in a multipart request and get its document record back. Use POST /document instead. - [Download binary file](/files-and-documents/download-binary): Deprecated. Download a file by redirect to a short-lived signed URL, with a bearer token or a public token. Use GET /document/:id or GET /document/public/:token instead. - **Data ingestion** - [Data ingestion](/data-ingestion): Send device and connected-system data into Ovok through the OVOK IoT Service. - [OVOK IoT Service](/data-ingestion/ovok-iot-service): Ingest device data through iot.ovok.com, protected by mutual TLS with setup support from the Ovok team. - **Cloud wearables** - [Cloud wearables](/wearables): Let patients connect their Oura, WHOOP, Strava, Withings, Polar, Ultrahuman or Suunto accounts to your project and receive the readings as FHIR Observations. - [Set up wearable providers](/wearables/set-up-providers): Register an OAuth app at Oura, WHOOP, Strava, Withings, Polar, Suunto or Ultrahuman, then save it in the Ovok Console or through the API so your patients can connect. - [Connect a patient's wearable](/wearables/connect-a-patient): Send a signed-in patient through the provider's consent page and back to your app, with @ovok/core, React hooks or plain HTTP. - [Sync and read wearable data](/wearables/sync-and-read-data): When wearable data reaches your project, what each provider syncs, what the Observations look like, and how to query them apart from device readings. - [Wearables API reference](/wearables/api-reference): Every cloud wearables route in full, with its caller, parameters, request, answer, status codes, side effects and per-provider behaviour. - **Email templates** - [Email templates](/email-templates): Choose which email template Ovok sends for each message in your project, with Brevo, SendGrid, Resend or SMTP, and manage the mapping through the API. - [Set up email sending](/email-templates/set-up-email): Take a project from no email to a mapped, ready template for each provider, then add languages and domains. - [Template catalogue and parameters](/email-templates/template-catalogue): Every email Ovok can send, who receives it, when it is sent, which parameters it carries, and how each provider writes a parameter. - [Map templates with the API](/email-templates/map-templates): List, read, create, replace and remove email template mappings with /v1/projects/me/email-templates, with every error and the cases that surprise people. - [Troubleshooting](/email-templates/troubleshooting): Why an Ovok email was not sent or looks wrong, and what to check in the template mapping and the project settings. - **CMS** - [CMS](/cms): Store legal pages, UI translations, release notes and your own content in the Ovok CMS, and read it from your apps with the public delivery API. - [List the items of a collection](/cms/list-collection-items): Read the published documents of a CMS collection, in one language, one page at a time, with your content and Ovok's shared content merged. - [Get an item by slug](/cms/get-collection-item): Read one published document of a CMS collection by its slug, with a fallback to Ovok's shared content. - [List the items of a content type](/cms/list-content-type-items): Read the published items of a content type you defined yourself, up to 100 at a time, with a CMS API key or a signed-in session. - [Use Ovok CMS with i18n](/cms/use-ovok-cms-with-i18n): Keep your app's UI text in the Ovok CMS and load it into i18next on the web and on mobile, with per-string fallback and a clear precedence. - **Video Calls** - [Video calls](/video-calls): Schedule video-call appointments, notify participants, and issue LiveKit join access with Ovok. - [Video-call appointments](/video-calls/appointments): Create, update, and list video-call FHIR Appointments in Ovok. - [Video-call join access](/video-calls/join-access): Request LiveKit join credentials for guests, Practitioners, and Patients. - [Video-call notifications and email](/video-calls/notifications-and-email): Grant participant access, queue active-call notifications, and send video-call emails. - **Ovok primitives** - [Ovok primitives](/primitives): Core platform routes and FHIR operations available in Ovok. - [Health and discovery](/primitives/health-and-discovery): Check Ovok API availability and discover OAuth, SMART, FHIR, and server capabilities. - [Get last observations](/primitives/last-observations): Retrieve recent FHIR Observation resources with the Ovok last-observations operation. - [Populate a Questionnaire](/primitives/populate-questionnaire): Pre-populate a FHIR QuestionnaireResponse with patient, observation, and supplied context data. - [Extract Observations from a QuestionnaireResponse](/primitives/extract-observations): Generate FHIR Observation resources from a saved QuestionnaireResponse. - [Expand a ValueSet](/primitives/expand-valueset): Expand a FHIR ValueSet into the codes available for selection or validation. - [Validate a code against a ValueSet](/primitives/validate-valueset-code): Check whether a code or Coding belongs to a FHIR ValueSet. - [Look up a CodeSystem concept](/primitives/lookup-codesystem): Retrieve display, designation, and property details for a CodeSystem concept. - [Validate a CodeSystem code](/primitives/validate-codesystem-code): Check whether a code exists in a FHIR CodeSystem and whether its display is correct. - [Test CodeSystem subsumption](/primitives/subsumes-codesystem): Determine the hierarchy relationship between two codes in a CodeSystem. - [Import a CodeSystem](/primitives/import-codesystem): Add concepts, properties, and designations to a CodeSystem through the Ovok import operation. - [Translate a concept using a ConceptMap](/primitives/translate-concept): Translate a coded concept between code systems or value sets using a ConceptMap. - [Validate a resource](/primitives/validate-resource): Validate a FHIR resource against the server's supported profiles and rules. - [Expunge a resource](/primitives/expunge-resource): Permanently remove a resource and its history from an Ovok project. - [Export a resource type as CSV](/primitives/export-csv): Export matching FHIR resources as a CSV file. - [Reindex a resource](/primitives/reindex-resource): Rebuild the search index entries for one FHIR resource. - [Get a resource graph](/primitives/resource-graph): Follow a GraphDefinition from a resource and return related resources in a Bundle. - [Get everything for a Patient](/primitives/patient-everything): Retrieve Patient compartment resources and resolved references in a searchset Bundle. - [Enable Signals for a Patient](/primitives/enable-signals-patient): Enroll an existing Ovok Patient in Signals with POST /fhir/R4/Patient/:id/$enable-signals. - [Set accounts for a Patient](/primitives/set-accounts-patient): Update Account references on a Patient and optionally propagate changes through its compartment. - [Set accounts for a resource](/primitives/set-accounts-resource): Set Account references on an individual FHIR resource. - [Resend a resource](/primitives/resend-resource): Replay subscription delivery for a stored FHIR resource. - [Merge resources by type](/primitives/merge-resources-by-type): Route reference and current contract status for the resource-type merge operation. - [Merge a resource by id](/primitives/merge-resource-by-id): Route reference and current contract status for the instance-level merge operation. - [Execute a Bot by id](/primitives/execute-bot-by-id): Run a stored Bot using its FHIR resource ID. - [Deploy a Bot](/primitives/deploy-bot): Deploy code for a stored Bot resource. - [Execute a Bot by identifier](/primitives/execute-bot-by-identifier): Run a Bot selected by its FHIR identifier. - [Clone a Project](/primitives/clone-project): Create a Project by cloning selected resources from an existing Project. - [Apply a PlanDefinition](/primitives/apply-plan-definition): Apply a FHIR PlanDefinition for a subject and create care-planning resources. - [Apply an ActivityDefinition](/primitives/apply-activity-definition): Apply a FHIR ActivityDefinition to a subject and create its defined activity resource. - [Export system data](/primitives/export-system-data): Start an asynchronous bulk export of the FHIR resources available in the current Ovok project. - [Export Patient data](/primitives/export-patient-data): Start an asynchronous bulk export of Patient resources and supported Patient-compartment data. - [Export Group data](/primitives/export-group-data): Start a bulk export for the resources associated with members of a FHIR Group. - [Check bulk export status](/primitives/check-bulk-export-status): Poll an export status URL and retrieve the completed Bulk Data manifest. - [Import data](/primitives/import-data): Route reference and support status for the listed bulk data import endpoint. - [Check asynchronous job status](/primitives/check-async-job-status): Read the status of a FHIR AsyncJob created by an asynchronous Ovok operation. - **Slim APIs** - [Slim APIs](/slim-apis): Reference for Ovok Slim API operations, organized by workflow. - Health - [Get health status](/slim-apis/health/get-health-status): Get health status using Ovok’s Slim API. - Patients - [Check patient name exists](/slim-apis/patient/check-patient-name-exists): Check patient name exists using Ovok’s Slim API. - [Create patient](/slim-apis/patient/create-patient): Create patient using Ovok’s Slim API. - [Delete patient](/slim-apis/patient/delete-patient): Delete patient using Ovok’s Slim API. - [Get patient](/slim-apis/patient/get-patient): Get patient using Ovok’s Slim API. - [Get patient $everything](/slim-apis/patient/get-patient-everything): Get patient $everything using Ovok’s Slim API. - [Get patient current vitals](/slim-apis/patient/get-patient-current-vitals): Get patient current vitals using Ovok’s Slim API. - [List patients](/slim-apis/patient/list-patients): List patients using Ovok’s Slim API. - [Update patient](/slim-apis/patient/update-patient): Update patient using Ovok’s Slim API. - Devices - [Create device](/slim-apis/device/create-device): Create device using Ovok’s Slim API. - [Delete device](/slim-apis/device/delete-device): Delete device using Ovok’s Slim API. - [Get device](/slim-apis/device/get-device): Get device using Ovok’s Slim API. - [List devices](/slim-apis/device/list-devices): List devices using Ovok’s Slim API. - [List live device rows](/slim-apis/device/list-live-device-rows): List live device rows using Ovok’s Slim API. - [List live telemetry and alerts by device](/slim-apis/device/list-live-telemetry-and-alerts-by-device): List live telemetry and alerts by device using Ovok’s Slim API. - [List projects for device assignment](/slim-apis/device/list-projects-for-device-assignment): List projects for device assignment using Ovok’s Slim API. - [Move device to another project](/slim-apis/device/move-device-to-another-project): Move device to another project using Ovok’s Slim API. - [Resolve how ingest routes device serials](/slim-apis/device/resolve-how-ingest-routes-device-serials): Resolve how ingest routes device serials using Ovok’s Slim API. - [Update device](/slim-apis/device/update-device): Update device using Ovok’s Slim API. - Locations - [Create location](/slim-apis/location/create-location): Create location using Ovok’s Slim API. - [Create location in a project (admin)](/slim-apis/location/create-location-in-a-project-admin): Create location in a project (admin) using Ovok’s Slim API. - [Delete location](/slim-apis/location/delete-location): Delete location using Ovok’s Slim API. - [Get location](/slim-apis/location/get-location): Get location using Ovok’s Slim API. - [List location assignment candidates](/slim-apis/location/list-location-assignment-candidates): List location assignment candidates using Ovok’s Slim API. - [List locations](/slim-apis/location/list-locations): List locations using Ovok’s Slim API. - [List locations in a project (admin)](/slim-apis/location/list-locations-in-a-project-admin): List locations in a project (admin) using Ovok’s Slim API. - [Update location](/slim-apis/location/update-location): Update location using Ovok’s Slim API. - Zones - [Create location in zone](/slim-apis/zones/create-location-in-zone): Create location in zone using Ovok’s Slim API. - [Delete location in zone](/slim-apis/zones/delete-location-in-zone): Delete location in zone using Ovok’s Slim API. - [Get zone detail](/slim-apis/zones/get-zone-detail): Get zone detail using Ovok’s Slim API. - [Get zone device counts](/slim-apis/zones/get-zone-device-counts): Get zone device counts using Ovok’s Slim API. - [List zones](/slim-apis/zones/list-zones): List zones using Ovok’s Slim API. - [Update location in zone](/slim-apis/zones/update-location-in-zone): Update location in zone using Ovok’s Slim API. - Organizations - [Create organization](/slim-apis/organization/create-organization): Create organization using Ovok’s Slim API. - [Delete organization](/slim-apis/organization/delete-organization): Delete organization using Ovok’s Slim API. - [Get organization](/slim-apis/organization/get-organization): Get organization using Ovok’s Slim API. - [Update organization](/slim-apis/organization/update-organization): Update organization using Ovok’s Slim API. - Projects - [Create a child project](/slim-apis/project/create-a-child-project): Create a child project using Ovok’s Slim API. - [Delete a child project](/slim-apis/project/delete-a-child-project): Delete a child project using Ovok’s Slim API. - [Get a project](/slim-apis/project/get-a-project): Get a project using Ovok’s Slim API. - [Get project device counts](/slim-apis/project/get-project-device-counts): Get project device counts using Ovok’s Slim API. - [Get project patient counts](/slim-apis/project/get-project-patient-counts): Get project patient counts using Ovok’s Slim API. - [List child project relationships](/slim-apis/project/list-child-project-relationships): List child project relationships using Ovok’s Slim API. - [List child projects with device counts](/slim-apis/project/list-child-projects-with-device-counts): List child projects with device counts using Ovok’s Slim API. - [List current project users](/slim-apis/project/list-current-project-users): List current project users using Ovok’s Slim API. - [List project users](/slim-apis/project/list-project-users): List project users using Ovok’s Slim API. - [Update a project](/slim-apis/project/update-a-project): Update a project using Ovok’s Slim API. - Access policies - [Create access policy](/slim-apis/policy/create-access-policy): Create access policy using Ovok’s Slim API. - [Delete access policy](/slim-apis/policy/delete-access-policy): Delete access policy using Ovok’s Slim API. - [Get access policy](/slim-apis/policy/get-access-policy): Get access policy using Ovok’s Slim API. - [List access policies](/slim-apis/policy/list-access-policies): List access policies using Ovok’s Slim API. - [Update access policy](/slim-apis/policy/update-access-policy): Update access policy using Ovok’s Slim API. - Practitioners - [Get practitioner](/slim-apis/practitioner/get-practitioner): Get practitioner using Ovok’s Slim API. - [Update practitioner](/slim-apis/practitioner/update-practitioner): Update practitioner using Ovok’s Slim API. - Invitations - [Invite practitioner](/slim-apis/invite/invite-practitioner): Invite practitioner using Ovok’s Slim API. - Notifications - [A medical notification's notes](/slim-apis/notifications/a-medical-notifications-notes): A medical notification's notes using Ovok’s Slim API. - [A raw Signals alert's notes](/slim-apis/notifications/a-raw-signals-alerts-notes): A raw Signals alert's notes using Ovok’s Slim API. - [Acknowledge medical notification](/slim-apis/notifications/acknowledge-medical-notification): Acknowledge medical notification using Ovok’s Slim API. - [Acknowledge notification](/slim-apis/notifications/acknowledge-notification): Acknowledge notification using Ovok’s Slim API. - [An alarm notification's notes](/slim-apis/notifications/an-alarm-notifications-notes): An alarm notification's notes using Ovok’s Slim API. - [An out-of-bed notification's notes](/slim-apis/notifications/an-out-of-bed-notifications-notes): An out-of-bed notification's notes using Ovok’s Slim API. - [Delete a note](/slim-apis/notifications/delete-a-note): Delete a note using Ovok’s Slim API. - [Edit a note's text](/slim-apis/notifications/edit-a-notes-text): Edit a note's text using Ovok’s Slim API. - [Get medical notification](/slim-apis/notifications/get-medical-notification): Get medical notification using Ovok’s Slim API. - [Leave a note on a notification](/slim-apis/notifications/leave-a-note-on-a-notification): Leave a note on a notification using Ovok’s Slim API. - [List alarm notifications](/slim-apis/notifications/list-alarm-notifications): List alarm notifications using Ovok’s Slim API. - [List medical notifications](/slim-apis/notifications/list-medical-notifications): List medical notifications using Ovok’s Slim API. - [List out-of-bed notifications](/slim-apis/notifications/list-out-of-bed-notifications): List out-of-bed notifications using Ovok’s Slim API. - [List recent alarm notifications](/slim-apis/notifications/list-recent-alarm-notifications): List recent alarm notifications using Ovok’s Slim API. - [List recent medical notifications](/slim-apis/notifications/list-recent-medical-notifications): List recent medical notifications using Ovok’s Slim API. - [List recent notifications](/slim-apis/notifications/list-recent-notifications): List recent notifications using Ovok’s Slim API. - [List recent out-of-bed notifications](/slim-apis/notifications/list-recent-out-of-bed-notifications): List recent out-of-bed notifications using Ovok’s Slim API. - [Replace a note's text](/slim-apis/notifications/replace-a-notes-text): Replace a note's text using Ovok’s Slim API. - Thresholds - [Get current org threshold (v2)](/slim-apis/threshold/get-current-org-threshold-v2): Get current org threshold (v2) using Ovok’s Slim API. - [Get current threshold](/slim-apis/threshold/get-current-threshold): Get current threshold using Ovok’s Slim API. - [Get patient effective threshold](/slim-apis/threshold/get-patient-effective-threshold): Get patient effective threshold using Ovok’s Slim API. - [Get patient effective threshold (v2)](/slim-apis/threshold/get-patient-effective-threshold-v2): Get patient effective threshold (v2) using Ovok’s Slim API. - [Get patient vital thresholds](/slim-apis/threshold/get-patient-vital-thresholds): Get patient vital thresholds using Ovok’s Slim API. - [Remove a patient vital threshold](/slim-apis/threshold/remove-a-patient-vital-threshold): Remove a patient vital threshold using Ovok’s Slim API. - [Set a patient vital threshold](/slim-apis/threshold/set-a-patient-vital-threshold): Set a patient vital threshold using Ovok’s Slim API. - [Update org threshold (v2)](/slim-apis/threshold/update-org-threshold-v2): Update org threshold (v2) using Ovok’s Slim API. - [Update patient threshold](/slim-apis/threshold/update-patient-threshold): Update patient threshold using Ovok’s Slim API. - [Update patient threshold (v2)](/slim-apis/threshold/update-patient-threshold-v2): Update patient threshold (v2) using Ovok’s Slim API. - [Update threshold](/slim-apis/threshold/update-threshold): Update threshold using Ovok’s Slim API. - Telemetry - [Get device telemetry and alerts](/slim-apis/telemetry/get-device-telemetry-and-alerts): Get device telemetry and alerts using Ovok’s Slim API. - [Get effective observation ids for a device](/slim-apis/telemetry/get-effective-observation-ids-for-a-device): Get effective observation ids for a device using Ovok’s Slim API. - [Get last-received observation ids for a device](/slim-apis/telemetry/get-last-received-observation-ids-for-a-device): Get last-received observation ids for a device using Ovok’s Slim API. - [List effective observation ids by device](/slim-apis/telemetry/list-effective-observation-ids-by-device): List effective observation ids by device using Ovok’s Slim API. - [List last-received observation ids by device](/slim-apis/telemetry/list-last-received-observation-ids-by-device): List last-received observation ids by device using Ovok’s Slim API. - Trends - [Get realtime trends graphs](/slim-apis/trends/get-realtime-trends-graphs): Get realtime trends graphs using Ovok’s Slim API. - [Get trends graphs](/slim-apis/trends/get-trends-graphs): Get trends graphs using Ovok’s Slim API. - Sleep - [Get sleep statistics](/slim-apis/sleep/get-sleep-statistics): Get sleep statistics using Ovok’s Slim API. - Reports and exports - [Create night report PDF](/slim-apis/export/create-night-report-pdf): Create night report PDF using Ovok’s Slim API. - Queries - [Count devices per zone (compatibility)](/slim-apis/query/count-devices-per-zone-compatibility): Count devices per zone (compatibility) using Ovok’s Slim API. - [Get resident devices with telemetry (compatibility)](/slim-apis/query/get-resident-devices-with-telemetry-compatibility): Get resident devices with telemetry (compatibility) using Ovok’s Slim API. - [List devices with telemetry (compatibility)](/slim-apis/query/list-devices-with-telemetry-compatibility): List devices with telemetry (compatibility) using Ovok’s Slim API. - [List telemetry and alerts (compatibility)](/slim-apis/query/list-telemetry-and-alerts-compatibility): List telemetry and alerts (compatibility) using Ovok’s Slim API. - [List users (compatibility)](/slim-apis/query/list-users-compatibility): List users (compatibility) using Ovok’s Slim API. - [List users across projects (compatibility)](/slim-apis/query/list-users-across-projects-compatibility): List users across projects (compatibility) using Ovok’s Slim API. - Users - [Delete user](/slim-apis/user/delete-user): Delete user using Ovok’s Slim API. - [Get all user consents](/slim-apis/user/get-all-user-consents): Get all user consents using Ovok’s Slim API. - [Get medical settings notifications consent](/slim-apis/user/get-medical-settings-notifications-consent): Deprecated. Get the medical settings notifications consent as a boolean. Use GET /v1/me/consent/medical instead. - [Get terms and conditions consent](/slim-apis/user/get-terms-and-conditions-consent): Get terms and conditions consent using Ovok’s Slim API. - [Send support message](/slim-apis/user/send-support-message): Send support message using Ovok’s Slim API. - [Update medical settings notifications consent](/slim-apis/user/update-medical-settings-notifications-consent): Deprecated. Give or withdraw the medical settings notifications consent. Use POST /v1/me/consent/medical instead. - [Update terms and conditions consent](/slim-apis/user/update-terms-and-conditions-consent): Update terms and conditions consent using Ovok’s Slim API. - [Update user consents](/slim-apis/user/update-user-consents): Update user consents using Ovok’s Slim API. - Audit - [List change history](/slim-apis/audit/list-change-history): List change history using Ovok’s Slim API. - [Change history keys](/slim-apis/audit/change-history-keys): Keys and values returned by Ovok change history. - **GraphQL** - [GraphQL](/graphql): Explore the Ovok GraphQL schema, query FHIR resources, and understand the sandbox resource catalog. - [GraphQL queries and pagination](/graphql/queries-and-pagination): Read Ovok FHIR resources by ID, search lists, select fields, and inspect connection-shaped results. - [GraphQL mutations](/graphql/mutations): Use the mutation fields and input types exposed by the Ovok GraphQL schema. - **FHIR resource catalogs** - [GraphQL FHIR resources A-C](/graphql/fhir-resources-a-c): GraphQL query fields, resource-specific search arguments, and mutations for FHIR R4 resources in the checked Ovok sandbox schema. - [GraphQL FHIR resources D-H](/graphql/fhir-resources-d-h): GraphQL query fields, resource-specific search arguments, and mutations for FHIR R4 resources in the checked Ovok sandbox schema. - [GraphQL FHIR resources I-O](/graphql/fhir-resources-i-o): GraphQL query fields, resource-specific search arguments, and mutations for FHIR R4 resources in the checked Ovok sandbox schema. - [GraphQL FHIR resources P-R](/graphql/fhir-resources-p-r): GraphQL query fields, resource-specific search arguments, and mutations for FHIR R4 resources in the checked Ovok sandbox schema. - [GraphQL FHIR resources S-Z](/graphql/fhir-resources-s-z): GraphQL query fields, resource-specific search arguments, and mutations for FHIR R4 resources in the checked Ovok sandbox schema. - [Ovok platform resource types](/graphql/ovok-platform-resources): Root GraphQL fields for non-FHIR platform types exposed in the inspected Ovok sandbox schema. - **FHIR Elements** - [FHIR Elements](/fhir-elements): FHIR R4 primitive and complex data type reference generated from the official HL7 package. - Primitive types - [base64Binary](/fhir-elements/primitive/base64-binary): Base StructureDefinition for base64Binary Type: A stream of bytes - [boolean](/fhir-elements/primitive/boolean): Base StructureDefinition for boolean Type: Value of "true" or "false" - [canonical](/fhir-elements/primitive/canonical): Base StructureDefinition for canonical type: A URI that is a reference to a canonical URL on a FHIR resource - [code](/fhir-elements/primitive/code): Base StructureDefinition for code type: A string which has at least one character and no leading or trailing whitespace and where there is no whitespace other than single spaces in the contents - [dateTime](/fhir-elements/primitive/date-time): Base StructureDefinition for dateTime Type: A date, date-time or partial date (e.g. just year or year + month). If hours and minutes are specified, a time zone SHALL be populated. The format is a union of the schema types gYear, gYearMonth, date and - [date](/fhir-elements/primitive/date): Base StructureDefinition for date Type: A date or partial date (e.g. just year or year + month). There is no time zone. The format is a union of the schema types gYear, gYearMonth and date. Dates SHALL be valid dates. - [decimal](/fhir-elements/primitive/decimal): Base StructureDefinition for decimal Type: A rational number with implicit precision - [id](/fhir-elements/primitive/id): Base StructureDefinition for id type: Any combination of letters, numerals, "-" and ".", with a length limit of 64 characters. (This might be an integer, an unprefixed OID, UUID or any other identifier pattern that meets these constraints.) Ids are c - [instant](/fhir-elements/primitive/instant): Base StructureDefinition for instant Type: An instant in time - known at least to the second - [integer](/fhir-elements/primitive/integer): Base StructureDefinition for integer Type: A whole number - [markdown](/fhir-elements/primitive/markdown): Base StructureDefinition for markdown type: A string that may contain Github Flavored Markdown syntax for optional processing by a mark down presentation engine - [oid](/fhir-elements/primitive/oid): Base StructureDefinition for oid type: An OID represented as a URI - [positiveInt](/fhir-elements/primitive/positive-int): Base StructureDefinition for positiveInt type: An integer with a value that is positive (e.g. >0) - [string](/fhir-elements/primitive/string): Base StructureDefinition for string Type: A sequence of Unicode characters - [time](/fhir-elements/primitive/time): Base StructureDefinition for time Type: A time during the day, with no date specified - [unsignedInt](/fhir-elements/primitive/unsigned-int): Base StructureDefinition for unsignedInt type: An integer with a value that is not negative (e.g. >= 0) - [uri](/fhir-elements/primitive/uri): Base StructureDefinition for uri Type: String of characters used to identify a name or a resource - [url](/fhir-elements/primitive/url): Base StructureDefinition for url type: A URI that is a literal reference - [uuid](/fhir-elements/primitive/uuid): Base StructureDefinition for uuid type: A UUID, represented as a URI - [xhtml](/fhir-elements/primitive/xhtml): Base StructureDefinition for xhtml Type - Complex types - [Address](/fhir-elements/complex/address): Base StructureDefinition for Address Type: An address expressed using postal conventions (as opposed to GPS or other location definition formats). This data type may be used to convey addresses for use in delivering mail as well as for visiting locat - [Age](/fhir-elements/complex/age): Base StructureDefinition for Age Type: A duration of time during which an organism (or a process) has existed. - [Annotation](/fhir-elements/complex/annotation): Base StructureDefinition for Annotation Type: A text note which also contains information about who made the statement and when. - [Attachment](/fhir-elements/complex/attachment): Base StructureDefinition for Attachment Type: For referring to data content defined in other formats. - [BackboneElement](/fhir-elements/complex/backbone-element): Base StructureDefinition for BackboneElement Type: Base definition for all elements that are defined inside a resource - but not those in a data type. - [CodeableConcept](/fhir-elements/complex/codeable-concept): Base StructureDefinition for CodeableConcept Type: A concept that may be defined by a formal reference to a terminology or ontology or may be provided by text. - [Coding](/fhir-elements/complex/coding): Base StructureDefinition for Coding Type: A reference to a code defined by a terminology system. - [ContactDetail](/fhir-elements/complex/contact-detail): Base StructureDefinition for ContactDetail Type: Specifies contact information for a person or organization. - [ContactPoint](/fhir-elements/complex/contact-point): Base StructureDefinition for ContactPoint Type: Details for all kinds of technology mediated contact points for a person or organization, including telephone, email, etc. - [Contributor](/fhir-elements/complex/contributor): Base StructureDefinition for Contributor Type: A contributor to the content of a knowledge asset, including authors, editors, reviewers, and endorsers. - [Count](/fhir-elements/complex/count): Base StructureDefinition for Count Type: A measured amount (or an amount that can potentially be measured). Note that measured amounts include amounts that are not precisely quantified, including amounts involving arbitrary units and floating currenc - [DataRequirement](/fhir-elements/complex/data-requirement): Base StructureDefinition for DataRequirement Type: Describes a required data item for evaluation in terms of the type of data, and optional code or date-based filters of the data. - [Distance](/fhir-elements/complex/distance): Base StructureDefinition for Distance Type: A length - a value with a unit that is a physical distance. - [Dosage](/fhir-elements/complex/dosage): Base StructureDefinition for Dosage Type: Indicates how the medication is/was taken or should be taken by the patient. - [Duration](/fhir-elements/complex/duration): Base StructureDefinition for Duration Type: A length of time. - [ElementDefinition](/fhir-elements/complex/element-definition): Base StructureDefinition for ElementDefinition Type: Captures constraints on each element within the resource, profile, or extension. - [Element](/fhir-elements/complex/element): Base StructureDefinition for Element Type: Base definition for all elements in a resource. - [Expression](/fhir-elements/complex/expression): Base StructureDefinition for Expression Type: A expression that is evaluated in a specified context and returns a value. The context of use of the expression must specify the context in which the expression is evaluated, and how the result of the exp - [Extension](/fhir-elements/complex/extension): Base StructureDefinition for Extension Type: Optional Extension Element - found in all resources. - [HumanName](/fhir-elements/complex/human-name): Base StructureDefinition for HumanName Type: A human's name with the ability to identify parts and usage. - [Identifier](/fhir-elements/complex/identifier): Base StructureDefinition for Identifier Type: An identifier - identifies some entity uniquely and unambiguously. Typically this is used for business identifiers. - [MarketingStatus](/fhir-elements/complex/marketing-status): Base StructureDefinition for MarketingStatus Type: The marketing status describes the date when a medicinal product is actually put on the market or the date as of which it is no longer available. - [Meta](/fhir-elements/complex/meta): Base StructureDefinition for Meta Type: The metadata about a resource. This is content in the resource that is maintained by the infrastructure. Changes to the content might not always be associated with version changes to the resource. - [Money](/fhir-elements/complex/money): Base StructureDefinition for Money Type: An amount of economic utility in some recognized currency. - [Narrative](/fhir-elements/complex/narrative): Base StructureDefinition for Narrative Type: A human-readable summary of the resource conveying the essential clinical and business information for the resource. - [ParameterDefinition](/fhir-elements/complex/parameter-definition): Base StructureDefinition for ParameterDefinition Type: The parameters to the module. This collection specifies both the input and output parameters. Input parameters are provided by the caller as part of the $evaluate operation. Output parameters are - [Period](/fhir-elements/complex/period): Base StructureDefinition for Period Type: A time period defined by a start and end date and optionally time. - [Population](/fhir-elements/complex/population): Base StructureDefinition for Population Type: A populatioof people with some set of grouping criteria. - [ProdCharacteristic](/fhir-elements/complex/prod-characteristic): Base StructureDefinition for ProdCharacteristic Type: The marketing status describes the date when a medicinal product is actually put on the market or the date as of which it is no longer available. - [ProductShelfLife](/fhir-elements/complex/product-shelf-life): Base StructureDefinition for ProductShelfLife Type: The shelf-life and storage information for a medicinal product item or container can be described using this class. - [Quantity](/fhir-elements/complex/quantity): Base StructureDefinition for Quantity Type: A measured amount (or an amount that can potentially be measured). Note that measured amounts include amounts that are not precisely quantified, including amounts involving arbitrary units and floating curr - [Range](/fhir-elements/complex/range): Base StructureDefinition for Range Type: A set of ordered Quantities defined by a low and high limit. - [Ratio](/fhir-elements/complex/ratio): Base StructureDefinition for Ratio Type: A relationship of two Quantity values - expressed as a numerator and a denominator. - [Reference](/fhir-elements/complex/reference): Base StructureDefinition for Reference Type: A reference from one resource to another. - [RelatedArtifact](/fhir-elements/complex/related-artifact): Base StructureDefinition for RelatedArtifact Type: Related artifacts such as additional documentation, justification, or bibliographic references. - [SampledData](/fhir-elements/complex/sampled-data): Base StructureDefinition for SampledData Type: A series of measurements taken by a device, with upper and lower limits. There may be more than one dimension in the data. - [Signature](/fhir-elements/complex/signature): Base StructureDefinition for Signature Type: A signature along with supporting context. The signature may be a digital signature that is cryptographic in nature, or some other signature acceptable to the domain. This other signature may be as simple - [SubstanceAmount](/fhir-elements/complex/substance-amount): Base StructureDefinition for SubstanceAmount Type: Chemical substances are a single substance type whose primary defining element is the molecular structure. Chemical substances shall be defined on the basis of their complete covalent molecular struc - [Timing](/fhir-elements/complex/timing): Base StructureDefinition for Timing Type: Specifies an event that may occur multiple times. Timing schedules are used to record when things are planned, expected or requested to occur. The most common usage is in dosage instructions for medications. - [TriggerDefinition](/fhir-elements/complex/trigger-definition): Base StructureDefinition for TriggerDefinition Type: A description of a triggering event. Triggering events can be named events, data events, or periodic, as determined by the type element. - [UsageContext](/fhir-elements/complex/usage-context): Base StructureDefinition for UsageContext Type: Specifies clinical/business/etc. metadata that can be used to retrieve, index and/or categorize an artifact. This metadata can either be specific to the applicable population (e.g., age category, DRG) o - Foundation models - [DomainResource](/fhir-elements/foundation/domain-resource): A resource that includes narrative, extensions, and contained resources. - [Resource](/fhir-elements/foundation/resource): This is the base resource type for everything. - Signals - **Signals** - [Signals](/signals): Understand Actimi Signals, its role alongside Ovok, and its MDR Class IIa status. - Standalone - [Signals standalone](/signals/standalone): Use Signals' own API through Ovok at /v1/signals/api: app types, authentication, what Ovok adds to each call, and a first patient, reading and alert. - [Signals webhooks](/signals/standalone/webhooks): Receive Signals alerts, episodes, readings and patient changes as signed webhooks: subscribers, events, reach, signatures and redelivery. - API reference - Alerts - [List alerts, or the episodes they were folded into](/signals/standalone/api/alerts/list-alerts-or-the-episodes-they-were-folded-into): List alerts, or the episodes they were folded into, through Ovok's Signals gateway. - [Get one alert, or one episode with its readings](/signals/standalone/api/alerts/get-one-alert-or-one-episode-with-its-readings): Get one alert, or one episode with its readings, through Ovok's Signals gateway. - [Acknowledge an alert, or an episode](/signals/standalone/api/alerts/acknowledge-an-alert-or-an-episode): Acknowledge an alert, or an episode, through Ovok's Signals gateway. - Observations - [Submit a reading by patient refId](/signals/standalone/api/observations/submit-a-reading-by-patient-refid): Submit a reading by patient refId, through Ovok's Signals gateway. - [List a patient's readings](/signals/standalone/api/observations/list-a-patients-readings): List a patient's readings, through Ovok's Signals gateway. - [Submit a reading for a patient id](/signals/standalone/api/observations/submit-a-reading-for-a-patient-id): Submit a reading for a patient id, through Ovok's Signals gateway. - [Submit several readings for one patient](/signals/standalone/api/observations/submit-several-readings-for-one-patient): Submit several readings for one patient, through Ovok's Signals gateway. - Patients - [List patients](/signals/standalone/api/patients/list-patients): List patients, through Ovok's Signals gateway. - [Create patients](/signals/standalone/api/patients/create-patients): Create patients, through Ovok's Signals gateway. - [Resolve refIds to Signals ids](/signals/standalone/api/patients/resolve-refids-to-signals-ids): Resolve refIds to Signals ids, through Ovok's Signals gateway. - [Get one patient](/signals/standalone/api/patients/get-one-patient): Get one patient, through Ovok's Signals gateway. - [Update a patient](/signals/standalone/api/patients/update-a-patient): Update a patient, through Ovok's Signals gateway. - [Delete a patient](/signals/standalone/api/patients/delete-a-patient): Delete a patient, through Ovok's Signals gateway. - [List a submitter's patients](/signals/standalone/api/patients/list-a-submitters-patients): List a submitter's patients, through Ovok's Signals gateway. - [Attach patients to a submitter](/signals/standalone/api/patients/attach-patients-to-a-submitter): Attach patients to a submitter, through Ovok's Signals gateway. - [Detach a patient from a submitter](/signals/standalone/api/patients/detach-a-patient-from-a-submitter): Detach a patient from a submitter, through Ovok's Signals gateway. - [List a subscriber's patients](/signals/standalone/api/patients/list-a-subscribers-patients): List a subscriber's patients, through Ovok's Signals gateway. - [Attach patients to a subscriber](/signals/standalone/api/patients/attach-patients-to-a-subscriber): Attach patients to a subscriber, through Ovok's Signals gateway. - [Detach a patient from a subscriber](/signals/standalone/api/patients/detach-a-patient-from-a-subscriber): Detach a patient from a subscriber, through Ovok's Signals gateway. - Patient config - [Write one config to many patients](/signals/standalone/api/patient-config/write-one-config-to-many-patients): Write one config to many patients, through Ovok's Signals gateway. - [Write a different config to each of many patients](/signals/standalone/api/patient-config/write-a-different-config-to-each-of-many-patients): Write a different config to each of many patients, through Ovok's Signals gateway. - [Read the config status of many patients](/signals/standalone/api/patient-config/read-the-config-status-of-many-patients): Read the config status of many patients, through Ovok's Signals gateway. - Clients - [List users](/signals/standalone/api/clients/list-users): List users, through Ovok's Signals gateway. - [Create a user](/signals/standalone/api/clients/create-a-user): Create a user, through Ovok's Signals gateway. - [Get one user](/signals/standalone/api/clients/get-one-user): Get one user, through Ovok's Signals gateway. - [Update a user](/signals/standalone/api/clients/update-a-user): Update a user, through Ovok's Signals gateway. - [Delete a user](/signals/standalone/api/clients/delete-a-user): Delete a user, through Ovok's Signals gateway. - [Read the webhook endpoint](/signals/standalone/api/clients/read-the-webhook-endpoint): Read the webhook endpoint, through Ovok's Signals gateway. - [Enable the webhook endpoint](/signals/standalone/api/clients/enable-the-webhook-endpoint): Enable the webhook endpoint, through Ovok's Signals gateway. - [Disable the webhook endpoint](/signals/standalone/api/clients/disable-the-webhook-endpoint): Disable the webhook endpoint, through Ovok's Signals gateway. - [Send a test event](/signals/standalone/api/clients/send-a-test-event): Send a test event, through Ovok's Signals gateway. - [Rotate the webhook signing secret](/signals/standalone/api/clients/rotate-the-webhook-signing-secret): Rotate the webhook signing secret, through Ovok's Signals gateway. - Tenants - [Get the caller's tenant](/signals/standalone/api/tenants/get-the-callers-tenant): Get the caller's tenant, through Ovok's Signals gateway. - [Update tenant settings](/signals/standalone/api/tenants/update-tenant-settings): Update tenant settings, through Ovok's Signals gateway. - Settings - [Get an org's rule settings](/signals/standalone/api/settings/get-an-orgs-rule-settings): Get an org's rule settings, through Ovok's Signals gateway. - [Replace an org's rule settings](/signals/standalone/api/settings/replace-an-orgs-rule-settings): Replace an org's rule settings, through Ovok's Signals gateway. - [Get a patient's own rule settings](/signals/standalone/api/settings/get-a-patients-own-rule-settings): Get a patient's own rule settings, through Ovok's Signals gateway. - [Replace a patient's own rule settings](/signals/standalone/api/settings/replace-a-patients-own-rule-settings): Replace a patient's own rule settings, through Ovok's Signals gateway. - Events - [List webhook events](/signals/standalone/api/events/list-webhook-events): List webhook events, through Ovok's Signals gateway. - [List a delivery's attempts](/signals/standalone/api/events/list-a-deliverys-attempts): List a delivery's attempts, through Ovok's Signals gateway. - [Redeliver events](/signals/standalone/api/events/redeliver-events): Redeliver events, through Ovok's Signals gateway. - [Redeliver one event](/signals/standalone/api/events/redeliver-one-event): Redeliver one event, through Ovok's Signals gateway. - Health - [Liveness](/signals/standalone/api/health/liveness): Liveness, through Ovok's Signals gateway. - **With Ovok** - [Signals with Ovok](/signals/connected-ovok): Use Signals as part of an Ovok app: FHIR Observations go to Signals, and its alerts come back into FHIR as DetectedIssue, Flag and Observation interpretation. - [Signals settings](/signals/with-ovok/settings): SIGNALS_SEND_AUTO and SIGNALS_ANNOTATE_OBSERVATIONS: what each switch does, its default, and what saving it sets up. - [Signals alerts in FHIR](/signals/with-ovok/fhir-annotations): How Ovok writes Signals' alerts and episodes into FHIR: a DetectedIssue per alert, a Flag per episode, and interpretation and referenceRange on the reading. - [Enable Signals for a Patient](/signals/enable-signals-patient): Enroll an existing Ovok Patient in Signals through the Patient enable-signals operation. - [Signals with Ovok gotchas](/signals/with-ovok/gotchas): What to know before relying on Signals in an Ovok app: which readings are sent, retries and gaps, episodes, annotation limits and access. - SDKs - **JavaScript SDK** - [JavaScript SDK](/web-sdk): Use the Ovok TypeScript client in server, browser, and React Native applications. - **Start here** - [Get started](/web-sdk/core/getting-started): Install @ovok/core, configure the Ovok client, and make a first request. - [Configure the client](/web-sdk/core/client-configuration): Configure the Ovok API origin, tenant, storage, and request behavior. - [The Ovok client](/web-sdk/core/next-client): Use the @ovok/core package root for Node, browsers and React Native. - [Errors and retries](/web-sdk/core/errors-and-retries): Catch typed Ovok errors and retry only failures that are safe to repeat. - [Compatibility](/web-sdk/core/compatibility): Check runtime, React, and Ovok API requirements before integrating the SDK. - Guides - [Sign users in](/web-sdk/core/guides/sign-in): Sign users in with the Ovok client, keep several accounts, and react to session changes. - [React applications](/web-sdk/core/guides/react-applications): Use the package root for the Ovok client and @ovok/core/next/react for the provider and hooks. Keep the provider and hook consumers in the client component tree. - Namespaces - [ovok.accounts](/web-sdk/core/namespaces/accounts): List the accounts the signed-in user can access. - [ovok.ai](/web-sdk/core/namespaces/ai): Translate text, chat with an AI assistant, and search FHIR data in natural language. - [ovok.auth](/web-sdk/core/namespaces/auth): Sign in, register, manage passwords and sessions, and delete an account. - [ovok.bots](/web-sdk/core/namespaces/bots): Run a server-side bot with a JSON input. - [ovok.cms](/web-sdk/core/namespaces/cms): Read published CMS documents, legal pages, release notes and translations. - [ovok.consent](/web-sdk/core/namespaces/consent): Read and record legal and medical consent, and request consent signatures. - [ovok.content](/web-sdk/core/namespaces/content): Search and manage managed content items by type and language. - [ovok.documents](/web-sdk/core/namespaces/documents): Register uploads, update metadata, delete documents, and build download URLs. - [ovok.email](/web-sdk/core/namespaces/email): List the project's email templates. - [ovok.fhir](/web-sdk/core/namespaces/fhir): Read, search, write and batch FHIR resources through the Ovok API. - [ovok.health](/web-sdk/core/namespaces/health): Check that the Ovok API and its dependencies are healthy. - [ovok.invites](/web-sdk/core/namespaces/invites): Send and accept record-share and care invitations. - [ovok.locales and ovok.localizations](/web-sdk/core/namespaces/localization): Manage the languages a project supports and its translation entries. - [ovok.measurements](/web-sdk/core/namespaces/measurements): Build, save and read measurements as FHIR Observations, with an offline queue. - [ovok.patient](/web-sdk/core/namespaces/patient): Read and manage the signed-in patient's own records. - [ovok.projects](/web-sdk/core/namespaces/projects): List the projects you administer and manage a project's members, features and settings. - [ovok.questionnaires and ovok.questionnaireResponses](/web-sdk/core/namespaces/questionnaires): Pre-fill questionnaires, submit responses and extract observations. - [ovok.realtime](/web-sdk/core/namespaces/realtime): Receive live notifications when FHIR resources change, over a websocket. - [ovok.session](/web-sdk/core/namespaces/session): Read the current session, follow session changes and switch between saved accounts. - [ovok.sharing](/web-sdk/core/namespaces/sharing): Share a patient's observations with other people through invites, and manage active shares. - [ovok.signals](/web-sdk/core/namespaces/signals): Enable Signals for a patient, repair undelivered limits and call the Signals API through Ovok. - [ovok.slim](/web-sdk/core/namespaces/slim): The care-home dashboard API: residents, devices, notifications, thresholds, trends, places and staff. - [ovok.videoCalls](/web-sdk/core/namespaces/videocalls): Schedule video calls, get access to them and send call emails. - [ovok.wearables](/web-sdk/core/namespaces/wearables): Configure wearable providers and connect, sync and disconnect a patient's accounts. - **React** - [React integration](/web-sdk/core/react): Provide the Ovok client to React components and use the SDK hooks. - **Reference** - [JavaScript SDK API map](/web-sdk/core/api-surface): Find the package-root client, resource namespaces, React exports, and API reference. - API reference - [API reference](/web-sdk/core/reference): Generated reference for every public export of the JavaScript SDK. - @ovok/core - [@ovok/core](/web-sdk/core/reference/core): Generated reference for @ovok/core. - [accounts](/web-sdk/core/reference/core/accounts): Generated reference for @ovok/core: accounts. - [ai](/web-sdk/core/reference/core/ai): Generated reference for @ovok/core: ai. - [auth](/web-sdk/core/reference/core/auth): Generated reference for @ovok/core: auth. - [bots](/web-sdk/core/reference/core/bots): Generated reference for @ovok/core: bots. - [cms](/web-sdk/core/reference/core/cms): Generated reference for @ovok/core: cms. - [consent](/web-sdk/core/reference/core/consent): Generated reference for @ovok/core: consent. - [content](/web-sdk/core/reference/core/content): Generated reference for @ovok/core: content. - [documents](/web-sdk/core/reference/core/documents): Generated reference for @ovok/core: documents. - [email](/web-sdk/core/reference/core/email): Generated reference for @ovok/core: email. - [Errors](/web-sdk/core/reference/core/errors): Generated reference for @ovok/core: Errors. - [fhir](/web-sdk/core/reference/core/fhir): Generated reference for @ovok/core: fhir. - [health](/web-sdk/core/reference/core/health): Generated reference for @ovok/core: health. - [invites](/web-sdk/core/reference/core/invites): Generated reference for @ovok/core: invites. - [localization](/web-sdk/core/reference/core/localization): Generated reference for @ovok/core: localization. - [measurements](/web-sdk/core/reference/core/measurements): Generated reference for @ovok/core: measurements. - [Ovok client](/web-sdk/core/reference/core/ovok-client): Generated reference for @ovok/core: Ovok client. - [patient](/web-sdk/core/reference/core/patient): Generated reference for @ovok/core: patient. - [projects](/web-sdk/core/reference/core/projects): Generated reference for @ovok/core: projects. - [questionnaires](/web-sdk/core/reference/core/questionnaires): Generated reference for @ovok/core: questionnaires. - [realtime](/web-sdk/core/reference/core/realtime): Generated reference for @ovok/core: realtime. - [session](/web-sdk/core/reference/core/session): Generated reference for @ovok/core: session. - [sharing](/web-sdk/core/reference/core/sharing): Generated reference for @ovok/core: sharing. - [signals](/web-sdk/core/reference/core/signals): Generated reference for @ovok/core: signals. - [slim](/web-sdk/core/reference/core/slim): Generated reference for @ovok/core: slim. - [videoCalls](/web-sdk/core/reference/core/videocalls): Generated reference for @ovok/core: videoCalls. - [wearables](/web-sdk/core/reference/core/wearables): Generated reference for @ovok/core: wearables. - [@ovok/core/next/react](/web-sdk/core/reference/react): Generated reference for @ovok/core/next/react. - **React Native SDK** - [React Native SDK](/native-sdk): Find the right guide, integration path, or component reference for @ovok/native - Guides - [Overview](/native-sdk/guide/overview): The end-to-end integration guide for the Ovok React Native SDK - [Getting started](/native-sdk/guide/getting-started): Build and run a first native sign-in flow with @ovok/native - How-to guides - [Installation and native setup](/native-sdk/guide/how-to/installation): Install @ovok/native and configure its Expo and native dependencies - [Build an app with the SDK](/native-sdk/guide/how-to/app-building): Assemble authentication, native health workflows, and app-owned screens with the OVOK SDK - [App shell and providers](/native-sdk/guide/how-to/app-shell): Initialize the OVOK client once and compose native, theme, navigation, and feature providers - [Invites and carers](/native-sdk/guide/how-to/invites-and-carers): Share a patient's record, accept care invites, and sign in carers from invite links. - [Logs and telemetry](/native-sdk/guide/how-to/observability): Route Ovok SDK diagnostics to your logger and OpenTelemetry providers - [Reusable flows](/native-sdk/guide/how-to/reusable-flows): Integrate Ovok pairing, manual measurement entry, and health access UI - [Troubleshooting](/native-sdk/guide/how-to/troubleshooting): Diagnose setup, permissions, Bluetooth, health import, and background failures - Modules - [Authentication](/native-sdk/guide/modules/auth): Build sign-in and account flows with the native SDK authentication components. - [Background sync](/native-sdk/guide/modules/background-sync): Choose and configure background delivery for Bluetooth and health data - [Breathing exercises](/native-sdk/guide/modules/breathing-exercise): Add the SDK's guided breathing exercise to a screen - [Device instructions](/native-sdk/guide/modules/bt-device-instructions): Show the instruction manual for an integrated Bluetooth device. - [Supported devices and catalog](/native-sdk/guide/modules/bt-device): Read catalog metadata, choose accepted device declarations, and present Bluetooth hardware. - [Bluetooth](/native-sdk/guide/modules/bt-management): Add Bluetooth LE devices, collect readings, and build a reliable connection flow. - [CMS content and translations](/native-sdk/guide/modules/cms): Read localized Ovok CMS content and load CMS translations into the host app - [Content components](/native-sdk/guide/modules/content): Compose content cards, lists, and FHIR Composition detail screens - [HealthKit and Health Connect](/native-sdk/guide/modules/data-sync): Configure permissions and import device health data with the OVOK SDK - [Diary cards](/native-sdk/guide/modules/diary): Present diary and measurement summaries with the DiaryCard component - [SDK helpers](/native-sdk/guide/modules/helpers): Reuse the SDK's form fields, conditional rendering, and utilities - [Optimized images](/native-sdk/guide/modules/image): Render Ovok-hosted images with the SDK's layout and URL helpers - [Journal components](/native-sdk/guide/modules/journal): Build a daily question, answer form, and past-entry list - [Language switcher](/native-sdk/guide/modules/language-switcher): Connect the controlled language selector to your app's localization layer - [Measurement lists](/native-sdk/guide/modules/measurement): Render measurement rows and date-range controls with app-owned data - [Background sync on the next client (preview)](/native-sdk/guide/modules/next-background-sync): Schedule health imports and offline measurement delivery with the next client - [Bluetooth in browsers (preview)](/native-sdk/guide/modules/next-bluetooth-web): @ovok/native/next/bluetooth/web: run the integrated devices in a browser over Web Bluetooth. - [Bluetooth on the next client (preview)](/native-sdk/guide/modules/next-bluetooth): @ovok/native/next/bluetooth: scan, pick, connect and save readings through the next Ovok client. - [Health sync on the next client (preview)](/native-sdk/guide/modules/next-data-sync): Import HealthKit and Health Connect records through the next client contract - [The next client (preview)](/native-sdk/guide/modules/next): @ovok/native/next: the @ovok/core/next contract with sessions kept in the device's secure store. - [Observation detail screens](/native-sdk/guide/modules/observation-detail): Present a normalized observation with the ObservationDetail primitives - [Patient components](/native-sdk/guide/modules/patient): Render patient cards and lists from app-owned FHIR resources - [PDF viewer](/native-sdk/guide/modules/pdf): Display a local or remote PDF with the native PDF viewer - [Picker sheets](/native-sdk/guide/modules/picker-sheet): Present a selection in a bottom sheet or wheel picker - [Polyfills](/native-sdk/guide/modules/polyfills): Install the runtime APIs the Ovok client needs before the app loads. - [Progress bars](/native-sdk/guide/modules/progress-bar): Show determinate task progress with the ProgressBar component - [Progress ring](/native-sdk/guide/modules/progress-ring): Show determinate or unknown progress in an accessible circular indicator - [Questionnaire forms](/native-sdk/guide/modules/questionnaire-response-form): Render and submit FHIR Questionnaires with the SDK form components - [Settings lists](/native-sdk/guide/modules/setting): Build grouped settings screens with the SettingList components - [Real-time sockets](/native-sdk/guide/modules/socket): Connect to Ovok real-time events with SocketProvider and its hooks - [Tabs](/native-sdk/guide/modules/tab): Compose controlled tab navigation with the Tab components - [Theme](/native-sdk/guide/modules/theme): Wrap the app in ThemeProvider and read colors, spacing and fonts with useAppTheme. - [Measurement tiles](/native-sdk/guide/modules/tile): Show measurement value, device status, and task state with Tile - [App UI](/native-sdk/guide/modules/ui): Compose Ovok UI components with your app shell, theme, and feature state - [Wearables](/native-sdk/guide/modules/wearables): Show a wearable provider's icon and display name. - Components - Auth Components - [Auth Components](/native-sdk/guide/components/auth): Choose the native SDK guide for each authentication flow - Sign-In - [Sign-In](/native-sdk/guide/components/auth/sign-in): Add email, social, and MFA sign-in to a React Native app - [SignIn Container](/native-sdk/guide/components/auth/sign-in/container): Main container component that wraps all sign-in related functionality with scrollable interface and keyboard handling - [SignIn.Footer](/native-sdk/guide/components/auth/sign-in/footer): Footer component for the sign-in screen with additional links and information - SignIn.EmailForm - [SignIn.EmailForm](/native-sdk/guide/components/auth/sign-in/form): Complete email/password authentication interface with validation and error handling - [SignIn.EmailForm.ForgotPassword](/native-sdk/guide/components/auth/sign-in/form/forgot-password): Forgot password link component for navigation to password recovery - [SignIn.EmailForm.Inputs](/native-sdk/guide/components/auth/sign-in/form/inputs): Email and password input fields with validation and error display - [SignIn.EmailForm.SigninButton](/native-sdk/guide/components/auth/sign-in/form/signin-button): Submit button component with loading states, validation, and error display - [SignIn.EmailForm.Spacing](/native-sdk/guide/components/auth/sign-in/form/spacing): Layout spacing component for consistent vertical spacing in SignIn forms - SignIn.Header - [SignIn.Header](/native-sdk/guide/components/auth/sign-in/header): Container component for the header section of the sign-in screen - [SignIn.Header.Description](/native-sdk/guide/components/auth/sign-in/header/description): Description component for the sign-in header section - [SignIn.Header.Title](/native-sdk/guide/components/auth/sign-in/header/title): Title component for the sign-in header section - [SignIn.RegisterLink](/native-sdk/guide/components/auth/sign-in/register-link): Localized link for navigating from sign-in to registration - SignIn.SocialLogins - [SignIn.SocialLogins](/native-sdk/guide/components/auth/sign-in/social-logins): Container component for grouping social login buttons with consistent styling and layout - [SignIn.SocialLogins.AppleLogin](/native-sdk/guide/components/auth/sign-in/social-logins/apple-login): Apple authentication button component with Sign in with Apple integration - [SignIn.SocialLogins.GoogleLogin](/native-sdk/guide/components/auth/sign-in/social-logins/google-login): Google authentication button component with OAuth integration and customizable appearance - [SignIn.LoginWithSeparator](/native-sdk/guide/components/auth/sign-in/social-logins/login-with-separator): Visual separator component to divide email login from social login options with an "OR" label - Registration - [Registration](/native-sdk/guide/components/auth/register): Create an account with the SDK's native registration form - components - Register.EmailForm - [Register.EmailForm](/native-sdk/guide/components/auth/register/components/form): The email form components provide a complete registration interface with validation, error handling, and customization options. - [Register.EmailForm.Inputs](/native-sdk/guide/components/auth/register/components/form/inputs): The inputs component provides all form fields needed for user registration including name, surname, email, password, and password confirmation. - [Register.EmailForm.RegisterButton](/native-sdk/guide/components/auth/register/components/form/register-button): The register button component handles form submission with loading states and error display. - [Register.EmailForm.Spacing](/native-sdk/guide/components/auth/register/components/form/spacing): The spacing component provides flexible layout spacing for form elements. This component is shared with the sign-in module. - Register.Header - [Register.Header](/native-sdk/guide/components/auth/register/components/header): The header component provides title and description sections for the registration interface. - [Register.Header.Description](/native-sdk/guide/components/auth/register/components/header/description): The description component displays subtitle text for the registration page. - [Register.Header.Title](/native-sdk/guide/components/auth/register/components/header/title): The title component displays the main heading for the registration page. - [Register](/native-sdk/guide/components/auth/register/components/register): The main Register component provides a complete registration interface with keyboard-aware scrolling and safe area handling. - examples - [Basic Usage](/native-sdk/guide/components/auth/register/examples/basic-usage): Learn how to implement basic user registration functionality with the Register component. - [Custom Styling](/native-sdk/guide/components/auth/register/examples/custom-styling): Learn how to customize the appearance of registration components to match your app's design. - [Custom Validation](/native-sdk/guide/components/auth/register/examples/custom-validation): Learn how to implement custom validation rules for the registration form. - [Pre-filled Data](/native-sdk/guide/components/auth/register/examples/pre-filled-data): Learn how to handle registration with pre-existing data and partial user information. - Password Reset - [Password Reset](/native-sdk/guide/components/auth/reset-password): Let users request password recovery with the native SDK - components - ResetPassword.Form - [ResetPassword.Form](/native-sdk/guide/components/auth/reset-password/components/form): Form container component that handles password reset validation, submission, and state management using Formik and Yup. - [ResetPassword.Form.Inputs](/native-sdk/guide/components/auth/reset-password/components/form/inputs): Input fields component containing email address input and account type selector for the password reset form. - [ResetPassword.Form.Button](/native-sdk/guide/components/auth/reset-password/components/form/reset-password-button): Submit button component for the password reset form with loading states, error display, and proper form integration. - [ResetPassword.Form.Spacing](/native-sdk/guide/components/auth/reset-password/components/form/spacing): Flexible spacing component that creates layout space between form elements and pushes the submit button to the bottom of the container. - ResetPassword.Header - [ResetPassword.Header](/native-sdk/guide/components/auth/reset-password/components/header): Header section component containing title and description for the password reset flow. - [ResetPassword.Header.Description](/native-sdk/guide/components/auth/reset-password/components/header/description): The description component for the password reset header, providing instructional text to guide users through the reset process. - [ResetPassword.Header.Title](/native-sdk/guide/components/auth/reset-password/components/header/title): The title component for the password reset header, displaying the main heading text. - [ResetPassword Container](/native-sdk/guide/components/auth/reset-password/components/reset-password): The main container component that provides keyboard awareness, safe area handling, and scrolling functionality for the password reset flow. - [Profile Form](/native-sdk/guide/components/auth/profile): Edit selected fields on the active Patient resource - [Session Management](/native-sdk/guide/components/auth/session): Read profile and server-side session state in your app - [Logout Button](/native-sdk/guide/components/auth/logout-button): Add a sign-out action to an account or settings screen - [Delete Account Button](/native-sdk/guide/components/auth/delete-account-button): Add a confirmed account-deletion action to account settings - [Auth Hooks and Rate Limits](/native-sdk/guide/components/auth/hooks): Handle session state and rate limits in custom auth screens - [Background sync APIs](/native-sdk/guide/components/background-sync): Components and functions for Bluetooth and health data background work - Breathing Exercise - [Breathing Exercise](/native-sdk/guide/components/breathing-exercise): A timed inhale, hold, and exhale compound component - [Breathing Exercise Types](/native-sdk/guide/components/breathing-exercise/types): Public TypeScript types for the breathing exercise module - [BreathingExerciseProvider](/native-sdk/guide/components/breathing-exercise/breathing-exercise-provider): Provider for the breathing exercise timeline - [useBreathingExercise](/native-sdk/guide/components/breathing-exercise/use-breathing-exercise): Read the active breathing exercise timeline - [BreathingExercise Component](/native-sdk/guide/components/breathing-exercise/breathing-exercise): The BreathingExercise component is the main compound component that provides a complete guided breathing exercise experience. It serves as both a container and a context provider, offering flexible composition through its sub-components. - Content - [Content](/native-sdk/guide/components/content): Content cards, lists, and FHIR Composition detail primitives - [ContentCard](/native-sdk/guide/components/content/content-card): Compound card primitives for content previews - [ContentDetail](/native-sdk/guide/components/content/content-detail): Compound primitives for rendering a FHIR Composition or supplied content - [ContentList](/native-sdk/guide/components/content/content-list): Flat-list primitives for grouped content and horizontal previews - [Composition context](/native-sdk/guide/components/content/advanced-api): Read a FHIR Composition from ContentDetail descendants - Helpers - [Helpers](/native-sdk/guide/components/helpers): Small utilities re-exported by @ovok/native - Conditional, Formik form fields, structured logging, and telemetry - [Conditional](/native-sdk/guide/components/helpers/conditional): Render children only when a boolean condition is true - Form helpers - [Form helpers](/native-sdk/guide/components/helpers/forms): Formik-connected text, select, and date fields - [FormDateField](/native-sdk/guide/components/helpers/forms/form-date-field): Formik-connected date, time, or datetime picker field - [FormField](/native-sdk/guide/components/helpers/forms/form-field): Formik-connected text input with touched error display - [FormSelect](/native-sdk/guide/components/helpers/forms/form-select): Formik-connected dropdown for a list of options - [Logging](/native-sdk/guide/components/helpers/logging): Configure the Ovok SDK logger and use its logging helpers - [Telemetry](/native-sdk/guide/components/helpers/telemetry): Configure OpenTelemetry providers and use Ovok spans, metrics, events, and attributes - [withAnimated](/native-sdk/guide/components/helpers/with-animated): Create a Reanimated-compatible wrapper for a component - LanguageSwitcher - [LanguageSwitcher](/native-sdk/guide/components/language-switcher): Controlled language selection compound component - [Language switcher hook and context](/native-sdk/guide/components/language-switcher/advanced-api): Read the selected language inside a LanguageSwitcher option - [LanguageSwitcher.Option.Flag](/native-sdk/guide/components/language-switcher/flag): A flexible container component for displaying language flags or flag-related imagery. Provides consistent styling and layout for visual language identification elements. - [LanguageSwitcher.Option.Label](/native-sdk/guide/components/language-switcher/label): A text component for displaying language names with consistent typography and styling. Built on react-native-paper Text component with custom font styling and theme integration. - [LanguageSwitcher](/native-sdk/guide/components/language-switcher/language-switcher): A provider component that manages language selection state through React Context API. Serves as the main container for language option components with automatic state management and theme integration. - [LanguageSwitcher.Option](/native-sdk/guide/components/language-switcher/language-switcher-option): A touchable option component for individual language selection. Provides context for its child components and handles selection state, styling, and interaction feedback with Material Design 3 theming. - [LanguageSwitcher.Option.RadioButton](/native-sdk/guide/components/language-switcher/radio-button): A radio button control component that automatically manages selection state based on the parent language option context. Built on react-native-paper RadioButton.Android with disabled pointer events for seamless parent option interaction. - Measurement - [Measurement](/native-sdk/guide/components/measurement): List and date-range primitives for measurement summaries - [MeasurementList](/native-sdk/guide/components/measurement/measurement-list): FlashList wrapper with default measurement rows - [MeasurementList.Item](/native-sdk/guide/components/measurement/measurement-list-item): Touchable value, unit, and timestamp row - [MeasurementList.DateRange](/native-sdk/guide/components/measurement/date-range): Navigate fixed week, month, or year ranges - [Polyfills and runtime setup](/native-sdk/guide/components/polyfills): Initialize runtime APIs required by the Ovok client - [ProgressBar](/native-sdk/guide/components/progress-bar): Animated progress indicator component with smooth spring transitions and SharedValue support - QuestionnaireResponseForm - [QuestionnaireResponseForm](/native-sdk/guide/components/questionnaire-response-form): A FHIR Questionnaire renderer with optional paging and submission - QuestionnaireForm.Content - [QuestionnaireForm.Content](/native-sdk/guide/components/questionnaire-response-form/content): Content primitives for current or all enabled questionnaire items - [QuestionnaireForm.Content.AllItems](/native-sdk/guide/components/questionnaire-response-form/content/all-items): Render all currently enabled questionnaire items - [QuestionnaireForm.Content.Error](/native-sdk/guide/components/questionnaire-response-form/content/error): Show validation and submission errors with retry - [QuestionnaireContent.Item](/native-sdk/guide/components/questionnaire-response-form/content/item): Renders the current questionnaire item for paged mode, automatically handling field type selection and conditional logic. - QuestionnaireForm.Navigation - [QuestionnaireForm.Navigation](/native-sdk/guide/components/questionnaire-response-form/navigation): Keyboard-aware navigation controls for the form - [QuestionnaireForm.Navigation.NextButton](/native-sdk/guide/components/questionnaire-response-form/navigation/next-button): Advance to the next enabled questionnaire item - [QuestionnaireForm.Navigation.PreviousButton](/native-sdk/guide/components/questionnaire-response-form/navigation/previous-button): Return to the previous questionnaire item - [QuestionnaireForm.Navigation.SubmitButton](/native-sdk/guide/components/questionnaire-response-form/navigation/submit-button): Submit the questionnaire response - [Providers, hooks, and submit utilities](/native-sdk/guide/components/questionnaire-response-form/advanced-api): Advanced public exports for custom questionnaire renderers - QuestionnaireHeader - [QuestionnaireHeader](/native-sdk/guide/components/questionnaire-response-form/header): The header components provide title display and progress tracking for questionnaire forms. These components work together to create informative headers that guide users through multi-page questionnaires with clear navigation context. - [QuestionnaireHeader.Subtitle](/native-sdk/guide/components/questionnaire-response-form/header/subtitle): Displays progress information using an app-owned i18next translation key and formatting. - [QuestionnaireHeader.Title](/native-sdk/guide/components/questionnaire-response-form/header/title): Displays the questionnaire title with automatic fallback and customizable styling. - Socket - [Socket](/native-sdk/guide/components/socket): Real-time socket connection to the Ovok backend - SocketProvider, useSocket hook, and AI event listener - [Socket hooks](/native-sdk/guide/components/socket/hooks): Read socket status and subscribe to AI session messages - [SocketProvider](/native-sdk/guide/components/socket/socket-provider): Create one authenticated Socket.IO connection for real-time SDK events - [Socket types and context](/native-sdk/guide/components/socket/types): Public types for the socket provider and its context - [ProgressRing](/native-sdk/guide/components/progress-ring): Accessible circular progress with a distinct unknown state and optional center content - Theme - [Theme](/native-sdk/guide/components/theme): Comprehensive theming system with Material Design 3, custom colors, and responsive spacing - [ThemeProvider](/native-sdk/guide/components/theme/theme-provider): Load SDK fonts and provide the generated React Native Paper theme - [useAppTheme](/native-sdk/guide/components/theme/use-app-theme): Read the generated Paper theme and SDK spacing helpers - [Colors](/native-sdk/guide/components/theme/colors): Default MD3 and healthcare color tokens exported by the SDK - [Multipliers](/native-sdk/guide/components/theme/multipliers): Configure spacing and border-radius calculation helpers - [WearableProviderIcon](/native-sdk/guide/components/wearables): Icons and display names for wearable providers and phone health stores - Tab - [Tab](/native-sdk/guide/components/tab): Flexible tab navigation component system with compound components and theme integration - [Tab Container](/native-sdk/guide/components/tab/tab): Main tab container component that manages state and provides context for tab items - [Tab.Item](/native-sdk/guide/components/tab/tab-item): Individual tab item component with icon support, active/inactive states, and theme integration - [TabProvider and useTab](/native-sdk/guide/components/tab/advanced-api): Share controlled tab state with a custom tab bar - Setting - [Setting](/native-sdk/guide/components/setting): High-performance setting list components with FlashList, custom items, and section headers - [SettingList](/native-sdk/guide/components/setting/setting-list): High-performance list component built on FlashList for settings screens with section headers and custom components - [SettingListItem](/native-sdk/guide/components/setting/setting-list-item): Individual setting item component with icon support, navigation arrows, and theme integration - [ListHeader](/native-sdk/guide/components/setting/list-header): Section header component for grouping settings with dynamic spacing based on position - PickerSheet - [PickerSheet](/native-sdk/guide/components/picker-sheet): Bottom sheet picker components with animated wheel picker for seamless selection experiences - [PickerSheet](/native-sdk/guide/components/picker-sheet/picker-sheet): Bottom sheet modal container with backdrop and safe area handling for picker interfaces - [WheelPicker](/native-sdk/guide/components/picker-sheet/wheel-picker): iOS-style wheel picker with smooth scrolling, visual feedback, and performance optimizations - Tile - [Tile](/native-sdk/guide/components/tile): Interactive measurement display components for healthcare applications with progress states and device connectivity - [Tile Component](/native-sdk/guide/components/tile/tile): Main container component for displaying measurement data with interactive states and visual feedback - [Tile.StartSection](/native-sdk/guide/components/tile/start-section): Header section component that displays task icon, completion state, and optional right-side content - [Tile.Title](/native-sdk/guide/components/tile/title): Title text component for displaying measurement task names with consistent typography - [Tile.TaskIcon](/native-sdk/guide/components/tile/task-icon): Healthcare measurement type icons with SVG rendering and theme integration - [Tile.MeasuredValue](/native-sdk/guide/components/tile/measured-value): Display component for measurement results with value, unit, and timestamp - [Tile.MeasuringSpinner](/native-sdk/guide/components/tile/measuring-spinner): Loading indicator component for active measurement states - [Tile.NoMeasuredValue](/native-sdk/guide/components/tile/no-measured-value): Placeholder component for displaying when no measurement data is available - [Tile.Status](/native-sdk/guide/components/tile/status): Device connection status indicator with colored text and localized messages - [Tile.CompleteAnimation](/native-sdk/guide/components/tile/complete-animation): Success animation overlay component for celebrating completed measurements - [CMS hooks](/native-sdk/guide/components/cms): Localized CMS content, legal pages, release notes, and i18next translations - Bluetooth device UI - [Bluetooth device UI](/native-sdk/guide/components/bt-device): Components for showing supported devices, connection status, and device instructions. - [BTDeviceInstruction](/native-sdk/guide/components/bt-device/bt-device-instruction): Display the SDK-hosted manual associated with an integrated Bluetooth device. - [BTDeviceItem](/native-sdk/guide/components/bt-device/bt-device-item): Compound themed row for Bluetooth device cards and lists. - [BTDeviceList](/native-sdk/guide/components/bt-device/bt-device-list): Render the SDK device rows and connection statuses from DeviceListProvider. - [DeviceListProvider](/native-sdk/guide/components/bt-device/device-list-provider): Provide the device-list context used by BTDeviceList and BTProvider status updates. - [SupportedDeviceList](/native-sdk/guide/components/bt-device/supported-device-list): Render the static device catalog with names, images, and image credits. - [Bluetooth device instructions](/native-sdk/guide/components/bt-device-instructions): Show an SDK-hosted instruction PDF for an integrated Bluetooth device. - Bluetooth management - [Bluetooth management](/native-sdk/guide/components/bt-management): Public Bluetooth runtime, provider, hooks, device definitions, and UI entry points. - [Advanced Bluetooth usage](/native-sdk/guide/components/bt-management/advanced-usage): Manage a Bluetooth session directly, tune runtime hooks, and use deterministic BLE fixtures. - [BluetoothDevicePicker](/native-sdk/guide/components/bt-management/bluetooth-device-picker): Render a chooser for multiple devices discovered by BTProvider auto-connect. - BTProvider - [BTProvider](/native-sdk/guide/components/bt-management/bt-provider): Own a Bluetooth LE session and receive device, connection, measurement, and error events. - [onAccessPermissionChanged](/native-sdk/guide/components/bt-management/bt-provider/on-access-permission-changed): React to the Bluetooth permission result reported by BTProvider. - [onConnectionEvent](/native-sdk/guide/components/bt-management/bt-provider/on-connection-event): Observe pairing, bond, battery, and transfer events from a Bluetooth device. - [onDeviceFound](/native-sdk/guide/components/bt-management/bt-provider/on-device-found): Handle a supported Bluetooth device when the SDK discovers it. - [onDeviceSelectionRequired](/native-sdk/guide/components/bt-management/bt-provider/on-device-selection-required): Present a user choice when auto-connect discovers multiple accepted devices. - [onDeviceStatusChanged](/native-sdk/guide/components/bt-management/bt-provider/on-device-status-changed): Observe compact status changes for accepted Bluetooth devices. - [onError](/native-sdk/guide/components/bt-management/bt-provider/on-error): Receive Bluetooth runtime and device measurement failures. - [onResult](/native-sdk/guide/components/bt-management/bt-provider/on-result): Receive normalized measurements from a Bluetooth device. - [Custom devices](/native-sdk/guide/components/bt-management/custom-devices): Declare a supported Bluetooth device when the SDK has no built-in protocol. - [DefaultPermissionFallback](/native-sdk/guide/components/bt-management/default-permission-fallback): Default permission prompt used when Bluetooth access has not been granted. - [Bluetooth services and protocols](/native-sdk/guide/components/bt-management/device-services): Public session and protocol APIs for Bluetooth integrations. - [Bluetooth device types](/native-sdk/guide/components/bt-management/device-types): Understand integrated device keys, decodable protocols, standard profiles, and custom declarations. - [Bluetooth hooks](/native-sdk/guide/components/bt-management/hooks): React hooks for Bluetooth runtime state, nearby devices, results, pairing, and scan control. - [BluetoothRuntimeProvider](/native-sdk/guide/components/bt-management/runtime-provider): Low-level provider for composing Bluetooth runtime hooks outside BTProvider. - [Supported devices](/native-sdk/guide/components/bt-management/supported-devices): Use the SDK's Bluetooth catalog to select supported devices and accepted protocol declarations. - [Bluetooth types](/native-sdk/guide/components/bt-management/types): Public types used by Bluetooth providers, results, errors, devices, and custom declarations. - DataSync - [DataSync](/native-sdk/guide/components/data-sync): Public module reference for HealthKit and Health Connect imports - android - [AndroidHealthConnectAuthorizationProvider](/native-sdk/guide/components/data-sync/android/health-connect-authorization-provider): Initialize Health Connect and request its permissions - [AndroidHealthSync](/native-sdk/guide/components/data-sync/android/health-sync): Read Health Connect records and save mapped OVOK observations - apple - [AppleHealthAuthorizationProvider](/native-sdk/guide/components/data-sync/apple/health-authorization-provider): Request HealthKit permissions and expose authorization state - [AppleHealthSync](/native-sdk/guide/components/data-sync/apple/health-sync): Read HealthKit samples and save mapped OVOK observations - [DataSync compound API](/native-sdk/guide/components/data-sync/data-sync): Platform component members exposed by the DataSync namespace - ManuallyRequestSheet - [ManuallyRequestSheet](/native-sdk/guide/components/data-sync/manually-request-sheet): Compound bottom-sheet UI for a host-owned health permission request - [ManuallyRequestSheet.Container](/native-sdk/guide/components/data-sync/manually-request-sheet/container): Themed bottom-sheet content with safe-area spacing - [ManuallyRequestSheet.Description](/native-sdk/guide/components/data-sync/manually-request-sheet/description): Themed body copy for a health permission prompt - [ManuallyRequestSheet.Image](/native-sdk/guide/components/data-sync/manually-request-sheet/image): Artwork slot for the health permission prompt - [ManuallyRequestSheet.RequestButton](/native-sdk/guide/components/data-sync/manually-request-sheet/request-button): Contained action button for requesting health permissions - [ManuallyRequestSheet.SkipButton](/native-sdk/guide/components/data-sync/manually-request-sheet/skip-button): Text action button for a host-defined skip action - [ManuallyRequestSheet.Title](/native-sdk/guide/components/data-sync/manually-request-sheet/title): Themed title text for a health permission prompt - [SyncProgressList](/native-sdk/guide/components/data-sync/sync-progress-list): Render per-type health import state and progress - diary - [Diary](/native-sdk/guide/components/diary): A specialized card component system for displaying health measurement data and diary entries in healthcare applications. The Diary module provides interactive cards with measurement display capabilities, designed for medical tracking and patient monitoring workflows. - [DiaryCard](/native-sdk/guide/components/diary/diary-card): The DiaryCard component is a specialized interactive card designed for displaying health measurement data and diary entries. It extends TouchableOpacity with healthcare-specific styling and automatic visual state management based on data availability. - [DiaryCard.MeasuredValue](/native-sdk/guide/components/diary/measured-value): Displays a measured value with unit and timestamp. Inherited from the Tile module and themed for consistency across diary cards. - [DiaryCard.NoData](/native-sdk/guide/components/diary/no-data): The DiaryCard.NoData component displays a localized message indicating that no measurement data is available. It provides consistent empty state messaging for diary cards with internationalization support and theme-aware styling. - [DiaryCard.StartSection](/native-sdk/guide/components/diary/start-section): The DiaryCard.StartSection component provides a horizontal layout container for the header section of diary cards. It manages the placement of icons, labels, and optional right-side content with automatic fallback to an arrow icon when no right content is provided. - [DiaryCard.TaskIcon](/native-sdk/guide/components/diary/task-icon): The DiaryCard.TaskIcon component displays a measurement type icon. It is inherited from the Tile module's TaskIcon and exposed on DiaryCard for convenience. - [DiaryCard.Title](/native-sdk/guide/components/diary/title): Title text for diary cards. Inherited from the Tile module and styled with large title typography. - [Image](/native-sdk/guide/components/image): Image rendering exports from the @ovok/native/image module - journal - [Journal](/native-sdk/guide/components/journal): A comprehensive set of React Native components for building journal and questionnaire interfaces in healthcare applications. The Journal module provides keyboard-aware forms, question-answer flows, and display components for past entries. - [Answer](/native-sdk/guide/components/journal/answer): The Answer component provides a keyboard-aware scrollable container for journal entry forms. It combines KeyboardAwareScrollView with KeyboardToolbar to create an optimal mobile input experience for healthcare questionnaires and journal entries. - [Answer.Actions](/native-sdk/guide/components/journal/answer-actions): The Answer.Actions component provides a themed container for action buttons at the bottom of journal entry forms. It applies consistent horizontal padding and serves as a wrapper for submission, navigation, and other form-related buttons. - [Answer.Header](/native-sdk/guide/components/journal/answer-header): The Answer.Header component provides a horizontal layout container for form navigation and action elements. It's designed to house back buttons, progress indicators, save actions, and other form-related controls at the top of journal entry screens. - [Answer.Input](/native-sdk/guide/components/journal/answer-input): The Answer.Input component renders React Native's TextInput with the - [Answer.Question](/native-sdk/guide/components/journal/answer-question): The Answer.Question component displays question text in journal and questionnaire forms. It provides consistent typography and styling for question prompts with emphasis on readability and accessibility. - [PastJournalCard](/native-sdk/guide/components/journal/past-journal-card): The PastJournalCard component displays completed journal entries and questionnaire responses in a Material Design card format. It provides a structured layout for showing historical health data, patient responses, and journal content. - [PastJournalCard.Answer](/native-sdk/guide/components/journal/past-journal-card-answer): The PastJournalCard.Answer component renders the answer text of a past journal entry with a subtle color. - [PastJournalCard.Date](/native-sdk/guide/components/journal/past-journal-card-date): The PastJournalCard.Date component displays the date of a past journal entry with subdued emphasis. - [PastJournalCard.Title](/native-sdk/guide/components/journal/past-journal-card-title): The PastJournalCard.Title component renders the title text for a past journal entry with appropriate typography and weight. - [TodayQuestionCard](/native-sdk/guide/components/journal/today-question-card): The TodayQuestionCard component displays daily questions and prompts in an interactive card format with gradient background support. It's designed for engaging daily health check-ins, questionnaires, and journal prompts with visual appeal and clear call-to-action elements. - [TodayQuestionCard.Actions](/native-sdk/guide/components/journal/today-question-card-actions): The TodayQuestionCard.Actions component aligns action items to the right with consistent spacing. - [TodayQuestionCard.Content](/native-sdk/guide/components/journal/today-question-card-content): The TodayQuestionCard.Content component is a padded container that spaces inner elements consistently. - [TodayQuestionCard.Gradient](/native-sdk/guide/components/journal/today-question-card-gradient): The TodayQuestionCard.Gradient component provides a gradient background using expo-linear-gradient for visually appealing daily prompts. - [TodayQuestionCard.Question](/native-sdk/guide/components/journal/today-question-card-question): The TodayQuestionCard.Question component renders the question text prominently. - [TodayQuestionCard.Title](/native-sdk/guide/components/journal/today-question-card-title): The TodayQuestionCard.Title component renders the card title with the theme primary color. - observation-detail - [ObservationDetail](/native-sdk/guide/components/observation-detail): The ObservationDetail module provides a comprehensive solution for displaying detailed healthcare observation data in a structured, scrollable layout. Built as a compound component system optimized for medical data presentation with support for various data types, photos, and resource information. - [ObservationDetail.DataContainer](/native-sdk/guide/components/observation-detail/data-container): A themed card container component for grouping related observation data with consistent styling, shadows, and Material Design 3 theming. Provides structured presentation for healthcare information. - [ObservationDetail.DateTime](/native-sdk/guide/components/observation-detail/date-time): A date and time display component with flexible formatting options and fallback content support. Designed for healthcare observation timestamps with localization and conditional rendering capabilities. - [ObservationDetail](/native-sdk/guide/components/observation-detail/observation-detail): A scrollable container component designed for displaying healthcare observation data with automatic safe area handling and theme-based spacing. Serves as the main container for compound observation detail components. - [ObservationDetail.PhotoPreview](/native-sdk/guide/components/observation-detail/photo-preview): An advanced photo gallery component with thumbnail navigation and full-screen modal preview. Built on FlashList for high performance with large photo sets and includes rotation, navigation, and zoom capabilities. - [ObservationDetail.Resource](/native-sdk/guide/components/observation-detail/resource): A resource information component for displaying data source details with optional imagery. Used to indicate the origin or method of data collection in healthcare observations. - [ObservationDetail.SingleCardDataRow](/native-sdk/guide/components/observation-detail/single-card-data-row): A label-value data row component designed for structured display of healthcare information. Provides consistent spacing, typography, and layout for observation data presentation. - [ObservationDetail.TextData](/native-sdk/guide/components/observation-detail/text-data): A text content component with consistent padding and typography for displaying narrative healthcare information. Designed for clinical notes, descriptions, and unstructured observation data. - [OptimizedImage](/native-sdk/guide/components/optimized-image): OptimizedImage is a layout and retry wrapper around expo-image. It reads the - patient - [Patient](/native-sdk/guide/components/patient): The Patient module provides a comprehensive solution for displaying and managing patient information in healthcare applications. Built on top of FHIR Patient resources with compound components, localization support, and performance optimizations using FlashList. - PatientCard.BirthDate - [PatientCard.BirthDate](/native-sdk/guide/components/patient/patient-birth-date): A compound component for displaying patient birth date and age information with calendar icon support. Provides flexible date formatting and age calculation for healthcare applications. - [PatientCard.BirthDate.Icon](/native-sdk/guide/components/patient/patient-birth-date/icon): Calendar icon component using Calendar2 from iconsax-react-nativejs. - [PatientCard.BirthDate.Text](/native-sdk/guide/components/patient/patient-birth-date/text): Date and age text display component with flexible formatting. - [PatientCard](/native-sdk/guide/components/patient/patient-card): A touchable card container component designed for displaying patient information in healthcare applications. Built with compound architecture to provide flexible composition of patient data elements with consistent Material Design 3 styling; add accessibility semantics through the touchable props in the host app. - [PatientCard.Gender](/native-sdk/guide/components/patient/patient-gender): A visual gender indicator component with icon support and themed styling. Provides accessible gender display for healthcare applications with localization support and multiple visual variants. - [PatientList](/native-sdk/guide/components/patient/patient-list): High-performance patient directory component built on FlashList for efficient rendering of large patient datasets. Provides default patient card rendering with customizable item rendering and automatic spacing. - [PatientCard.Meta](/native-sdk/guide/components/patient/patient-meta): A flexible container component for organizing patient metadata and secondary information. Provides consistent spacing and responsive layout for patient demographics and additional details. - [PatientCard.Name](/native-sdk/guide/components/patient/patient-name): A text component for displaying patient names with configurable formatting options. Built to work with FHIR HumanName resources and provides intelligent fallbacks for missing or incomplete name data in healthcare applications. - PDF - [PDF](/native-sdk/guide/components/pdf): Theme-aware native PDF rendering with loading and callback support - [PDFViewer](/native-sdk/guide/components/pdf/pdf-viewer): PDF document viewer component built on react-native-pdf with enhanced loading states, theme integration, and automatic error handling. Provides a seamless document viewing experience with smooth loading animations and consistent styling. - App UI components - [App UI components](/native-sdk/guide/components/ui): Public component and theming exports from @ovok/native/ui - [BackgroundSyncSetting](/native-sdk/guide/components/ui/background-sync-setting): Let a user enable Bluetooth background scanning - [BluetoothStateNotice](/native-sdk/guide/components/ui/bluetooth-state-notice): Show Bluetooth availability and offer a permission or settings action - [Calendar](/native-sdk/guide/components/ui/calendar): Accessible, locale-aware date selection for React Native apps - [EcgStripViewer](/native-sdk/guide/components/ui/ecg-strip-viewer): Render app-provided ECG samples as a paged, scrollable waveform strip. - [FlowFrame](/native-sdk/guide/components/ui/flow-frame): Safe-area and keyboard-aware layout for host-owned multi-step flows - [HealthImportCard](/native-sdk/guide/components/ui/health-import-card): Show health access status and request access through an app adapter - [ManualEntryForm](/native-sdk/guide/components/ui/manual-entry-form): Collect and validate a numeric measurement for app-owned saving - [MeasurementTrendChart](/native-sdk/guide/components/ui/measurement-trend-chart): Themeable multi-series measurement trend and sparkline charts - [NearbyDevicePrompt](/native-sdk/guide/components/ui/nearby-device-prompt): An opt-in prompt for pairing a supported device found nearby - [PairingFlow](/native-sdk/guide/components/ui/pairing-flow): Pair a discovered Bluetooth device and persist its ID - [PeriodCalendar](/native-sdk/guide/components/ui/period-calendar): Calendar-based visualization for host-supplied period intervals - [PrerequisiteGate](/native-sdk/guide/components/ui/prerequisite-gate): Wait for a host-authorized account prerequisite check before rendering app content - [Reusable flows](/native-sdk/guide/components/ui/reusable-flows): Higher-level pairing, manual-entry, and health-access UI components - [SupportedDevicesCatalogList](/native-sdk/guide/components/ui/supported-devices-catalog-list): Display the supported-device catalog with connection declarations and measurement metadata. - [UrineTestResult](/native-sdk/guide/components/ui/urine-test-result): Present an existing urine-analysis measurement with accessible analyte rows - [Architecture and lifecycle](/native-sdk/guide/architecture): Understand SDK boundaries, provider lifetimes, and foreground and background data flows. - [Reusable SDK features](/native-sdk/guide/sdk-features): The SDK exposes integration primitives and optional composition UI, not an application - [Measurement field catalog](/native-sdk/guide/measurement-field-catalog): Canonical units, validation bounds, and optional display ranges for decoded measurements - [Public API map](/native-sdk/guide/public-api): Root exports and subpath imports for @ovok/native - [Development and release](/native-sdk/guide/release-and-development): Build, test, document, and release the SDK safely - [Supported Bluetooth devices](/native-sdk/guide/supported-devices): Complete list of devices in the @ovok/native Bluetooth catalog. - API reference - [API reference](/native-sdk/reference): Generated reference for every public module of @ovok/native. - [@ovok/native/auth](/native-sdk/reference/auth): Sign-in, registration, password reset, profile and session components and hooks. - [@ovok/native/background-sync](/native-sdk/reference/background-sync): Android background sync, the Bluetooth foreground service, sync notifications and background-safe storage. - [@ovok/native/breathing-exercise](/native-sdk/reference/breathing-exercise): Guided breathing exercise component, provider and hook. - [@ovok/native/bt-device-instructions](/native-sdk/reference/bt-device-instructions): Device instruction screens and the bundled instruction PDFs for supported Bluetooth devices. - [@ovok/native/bt-device](/native-sdk/reference/bt-device): Bluetooth device lists, the supported-device catalog and device images. - [@ovok/native/bt-management](/native-sdk/reference/bt-management): Bluetooth Low Energy device management: scanning, pairing, connecting, reading measurements and syncing them. - [@ovok/native/cms](/native-sdk/reference/cms): Hooks that read CMS content, locales and translations. - [@ovok/native/content](/native-sdk/reference/content): Content cards, lists and detail screens for educational content. - [@ovok/native/data-sync](/native-sdk/reference/data-sync): Apple Health and Health Connect import: authorization, sync progress and manual request UI. - [@ovok/native/diary](/native-sdk/reference/diary): Diary card component. - [@ovok/native/helpers](/native-sdk/reference/helpers): Shared helpers: form fields, conditional rendering, formatting utilities, logging and telemetry. - [@ovok/native/image](/native-sdk/reference/image): Optimized image component. - [@ovok/native/journal](/native-sdk/reference/journal): Journal question, answer and history components. - [@ovok/native/language-switcher](/native-sdk/reference/language-switcher): Language switcher component, context and hook. - [@ovok/native/measurement](/native-sdk/reference/measurement): Measurement list components. - [@ovok/native/next/background-sync](/native-sdk/reference/next-background-sync): Background sync helpers for `@ovok/native/next`. - [@ovok/native/next/bluetooth](/native-sdk/reference/next-bluetooth): Bluetooth for `@ovok/native/next`, in layers: the framework-free `BluetoothRuntime` and its ports, device drivers, - [@ovok/native/next/bluetooth/web](/native-sdk/reference/next-bluetooth-web): Bluetooth for `@ovok/native/next` in browsers, over Web Bluetooth: the same `BluetoothRuntime`, devices, result - [@ovok/native/next/data-sync](/native-sdk/reference/next-data-sync): HealthKit and Health Connect import for `@ovok/native/next`. - [@ovok/native/next](/native-sdk/reference/next): The native `Ovok` client built on `@ovok/core/next`, with secure-store token storage. - [@ovok/native/observation-detail](/native-sdk/reference/observation-detail): Observation detail screen components. - [@ovok/native/patient](/native-sdk/reference/patient): Patient card, list, name, gender and birth-date components. - [@ovok/native/pdf](/native-sdk/reference/pdf): PDF viewer component. - [@ovok/native/picker-sheet](/native-sdk/reference/picker-sheet): Wheel pickers and the bottom picker sheet. - [@ovok/native/polyfills](/native-sdk/reference/polyfills): Runtime polyfills the SDK needs in React Native; import once at app start. - [@ovok/native/progress-bar](/native-sdk/reference/progress-bar): Progress bar component. - [@ovok/native/progress-ring](/native-sdk/reference/progress-ring): Progress ring component. - [@ovok/native/questionnaire-response-form](/native-sdk/reference/questionnaire-response-form): FHIR questionnaire forms: providers, navigation, item renderers and answer helpers. - [@ovok/native/setting](/native-sdk/reference/setting): Settings list components. - [@ovok/native/socket](/native-sdk/reference/socket): Realtime socket provider and context. - [@ovok/native/tab](/native-sdk/reference/tab): Tab bar component, provider, context and hook. - [@ovok/native/theme](/native-sdk/reference/theme): Theme provider, theme hook, colors and spacing multipliers. - [@ovok/native/tile](/native-sdk/reference/tile): Tile component for task and dashboard entries. - [@ovok/native/ui](/native-sdk/reference/ui): Ready-made measurement and device flows: pairing, manual entry, charts, calendars and ECG strips. - [@ovok/native/wearables](/native-sdk/reference/wearables): Wearable provider icons and wearable source types. - **Python SDK** - [Python SDK](/python-sdk): Integrate Ovok Core APIs from Python applications. - [Get started](/python-sdk/getting-started): The Python SDK requires Python 3.10 or later and has no runtime dependencies outside - Guides - [Handle errors](/python-sdk/guides/handle-errors): Every failure raises a subclass of OvokError. Catch the specific class you can act on and - [Keep users signed in](/python-sdk/guides/keep-users-signed-in): A client built without client_secret acts for a signed-in user. It keeps the session in - [Authenticate a machine client](/python-sdk/guides/machine-clients): A backend service that acts for itself, not for a user, signs in with an OAuth client ID and - [Page through lists](/python-sdk/guides/paging): Every list method returns a Page: data holds this page's items, total the item count - Namespaces - [Accounts](/python-sdk/namespaces/accounts): ovok.accounts lists the accounts the signed-in user can access, for example to offer an account - [AI](/python-sdk/namespaces/ai): ovok.ai gives access to Ovok's AI features: text translation, natural-language FHIR search and - [Authentication](/python-sdk/namespaces/auth): ovok.auth signs users in and out, registers patients and practitioners, and manages the - [Bots](/python-sdk/namespaces/bots): ovok.bots runs bots deployed to the project and returns their output. - [CMS](/python-sdk/namespaces/cms): ovok.cms reads the published CMS content of the client's tenant: legal pages, translations, - [Consent](/python-sdk/namespaces/consent): ovok.consent reads and records the signed-in user's legal consent (terms and conditions) - [Content](/python-sdk/namespaces/content): ovok.content manages the project's CMS content entries. Each entry is addressed by its content - [Documents](/python-sdk/namespaces/documents): ovok.documents stores files such as reports and consent forms. It lists and deletes documents, - [Email](/python-sdk/namespaces/email): ovok.email reads the project's email settings. Today it lists the email templates the project - [FHIR](/python-sdk/namespaces/fhir): ovok.fhir is a generic FHIR R4 client for any resource type. Resources are plain - [Health](/python-sdk/namespaces/health): ovok.health reads the API health check. It sends no credentials, so it works before - [Invites](/python-sdk/namespaces/invites): ovok.invites creates and accepts invitations: record shares, care, practitioners and - [Locales](/python-sdk/namespaces/locales): ovok.locales reads and updates the languages the project offers. Use - [Localizations](/python-sdk/namespaces/localizations): ovok.localizations manages the project's localized strings by language and key, either one - [Measurements](/python-sdk/namespaces/measurements): ovok.measurements turns device and manual readings into FHIR Observations, saves them - [Patient](/python-sdk/namespaces/patient): ovok.patient reads the FHIR data a patient app needs: appointments, schedules and slots, - [Projects](/python-sdk/namespaces/projects): ovok.projects lists the projects the signed-in user administers and manages the current - [Questionnaire responses](/python-sdk/namespaces/questionnaire-responses): ovok.questionnaire_responses submits a user's answers, optionally with the Observations - [Questionnaires](/python-sdk/namespaces/questionnaires): ovok.questionnaires prepares a Questionnaire for the user to fill in. Read Questionnaires - [Sharing](/python-sdk/namespaces/sharing): ovok.sharing lets a patient share their record: create and redeem invitations, list and - [Signals](/python-sdk/namespaces/signals): ovok.signals turns on Signals monitoring for patients, calls the Signals API through the - [Slim](/python-sdk/namespaces/slim): ovok.slim is Ovok's compact care-facility API: patients, devices and their live state, - [Video calls](/python-sdk/namespaces/video-calls): ovok.video_calls runs LiveKit video calls: it schedules call appointments, grants room access to - [Wearables](/python-sdk/namespaces/wearables): ovok.wearables configures the project's wearable providers and connects the signed-in user's - API reference - [API reference](/python-sdk/reference): Every public module, class and method of the Python SDK. - auth - [ovok_sdk.auth.access_token](/python-sdk/reference/auth/access_token): OAuth access tokens and their expiry. - [ovok_sdk.auth.client_credentials](/python-sdk/reference/auth/client_credentials): Machine-client authentication with the OAuth client-credentials grant. - [ovok_sdk.auth.credentials](/python-sdk/reference/auth/credentials): The interface that supplies bearer tokens to requests. - [ovok_sdk.auth.pkce](/python-sdk/reference/auth/pkce): PKCE (RFC 7636) verifier and challenge generation. - [ovok_sdk.auth.session](/python-sdk/reference/auth/session): The signed-in user's session and its JSON conversions. - [ovok_sdk.auth.session_store](/python-sdk/reference/auth/session_store): Persistence of the user session in `OvokStorage`. - [ovok_sdk.auth.single_flight](/python-sdk/reference/auth/single_flight): Deduplication of concurrent calls, such as token refreshes. - [ovok_sdk.auth.token_endpoint](/python-sdk/reference/auth/token_endpoint): The OAuth token endpoint. - [ovok_sdk.auth.user_session](/python-sdk/reference/auth/user_session): The signed-in user's session and its token refresh. - [ovok_sdk.clock](/python-sdk/reference/clock): Wall-clock time in epoch milliseconds. - [ovok_sdk.config](/python-sdk/reference/config): Client options, checked once when the client is constructed. - [ovok_sdk.errors](/python-sdk/reference/errors): Errors the SDK raises, one class per kind of failure. - http - [ovok_sdk.http.client](/python-sdk/reference/http/client): The authenticated JSON client every namespace sends through. - [ovok_sdk.http.error_response](/python-sdk/reference/http/error_response): Conversion of error responses into `OvokError`s. - [ovok_sdk.http.request](/python-sdk/reference/http/request): The description of one API call before it is sent. - [ovok_sdk.http.retry](/python-sdk/reference/http/retry): When and how long to wait before retrying a request. - [ovok_sdk.http.transport](/python-sdk/reference/http/transport): The pluggable transport that sends HTTP requests. - [ovok_sdk.http.url](/python-sdk/reference/http/url): URL path and query encoding. - [ovok_sdk.ovok](/python-sdk/reference/ovok): The `Ovok` client, the entry point to every API namespace. - [ovok_sdk.paging](/python-sdk/reference/paging): Paged results and the loaders for each list format Ovok returns. - resources - [ovok_sdk.resources.ai](/python-sdk/reference/resources/ai): AI features: translation, natural-language FHIR search and chat. - [ovok_sdk.resources.auth](/python-sdk/reference/resources/auth): The `ovok.auth` namespace: sign-in, registration and account management. - [ovok_sdk.resources.auth_sessions](/python-sdk/reference/resources/auth_sessions): The `ovok.auth.sessions` namespace: the account's server-side sessions. - [ovok_sdk.resources.bots](/python-sdk/reference/resources/bots): The `ovok.bots` namespace: run server-side bots. - [ovok_sdk.resources.cms](/python-sdk/reference/resources/cms): Published CMS content for the configured tenant: documents, legal pages and translations. - [ovok_sdk.resources.consent](/python-sdk/reference/resources/consent): The `ovok.consent` namespace: the signed-in user's legal and medical consent. - [ovok_sdk.resources.content](/python-sdk/reference/resources/content): Project-managed CMS content by content type and language. - [ovok_sdk.resources.documents](/python-sdk/reference/resources/documents): Stored documents: uploads, metadata and download links. - [ovok_sdk.resources.fhir](/python-sdk/reference/resources/fhir): Generic FHIR R4 reads, searches, writes and batches. - [ovok_sdk.resources.health](/python-sdk/reference/resources/health): The `ovok.health` namespace: the API health check. - [ovok_sdk.resources.invites](/python-sdk/reference/resources/invites): The `ovok.invites` namespace: create and accept invitations. - [ovok_sdk.resources.measurement_builder](/python-sdk/reference/resources/measurement_builder): Builds FHIR Observations from typed measurement readings. - [ovok_sdk.resources.measurements](/python-sdk/reference/resources/measurements): Measurement building, saving with an offline retry queue, and latest-value reads. - [ovok_sdk.resources.patient](/python-sdk/reference/resources/patient): FHIR reads for the signed-in patient: appointments, measurements, care plans and more. - [ovok_sdk.resources.platform](/python-sdk/reference/resources/platform): Platform administration: accounts, projects, locales, localizations and email templates. - [ovok_sdk.resources.questionnaires](/python-sdk/reference/resources/questionnaires): Questionnaire population and QuestionnaireResponse submission and extraction. - [ovok_sdk.resources.registration](/python-sdk/reference/resources/registration): Sign-up requests for `ovok.auth.register` and `ovok.auth.register_practitioner`. - [ovok_sdk.resources.sharing](/python-sdk/reference/resources/sharing): The `ovok.sharing` namespace: patient record sharing. - [ovok_sdk.resources.signals](/python-sdk/reference/resources/signals): Signals enablement, the proxied Signals API and undeliverable-config repair. - [ovok_sdk.resources.slim](/python-sdk/reference/resources/slim): The `ovok.slim` namespace: Ovok's compact care-facility API for patients, devices and alerts. - [ovok_sdk.resources.video_calls](/python-sdk/reference/resources/video_calls): LiveKit video calls: appointments, access and email notices. - [ovok_sdk.resources.wearables](/python-sdk/reference/resources/wearables): Wearable providers and the signed-in user's connections to them. - [ovok_sdk.storage](/python-sdk/reference/storage): Key-value storage the client keeps its session in. - [ovok_sdk.values](/python-sdk/reference/values): Checks on loosely typed values. - **Java SDK** - [Java SDK](/java-sdk): Integrate Ovok Core from Java applications. - [Get started](/java-sdk/getting-started): The Java SDK requires Java 17 or later. - [Client configuration](/java-sdk/client-configuration): Build an OvokConfig and pass it to new Ovok(config). - [Errors and retries](/java-sdk/errors-and-retries): Errors - **Namespaces** - [Authentication](/java-sdk/authentication): Sign in - [FHIR and observations](/java-sdk/fhir-and-observations): ovok.fhir() calls the FHIR R4 API. - [Measurements](/java-sdk/measurements): ovok.measurements().save(parameters) builds FHIR Observations from a measurement and saves them in a transaction: - [Patient](/java-sdk/patient): ovok.patient() reads and writes the signed-in patient's FHIR records: appointments, care plans, devices, measurements, notifications, questionnaires and responses, schedules and slots, plus the dashboard and latest observations. - [Slim APIs](/java-sdk/slim-apis): ovok.slim() groups the care home routes. Methods return a JsonNode or a Page, and request bodies accept a Map or a JsonNode. - [Signals](/java-sdk/signals): ovok.signals() manages Signals delivery for patients. - [Projects](/java-sdk/projects): ovok.projects() lists projects and manages the current project's members(), features() and settings(). - [Accounts](/java-sdk/accounts): ovok.accounts() lists the accounts the caller can see. - [Invites](/java-sdk/invites): ovok.invites() creates and accepts invites. Each flow, recordShares() and care(), has create and accept. practitioners() and patients() are deprecated. - [Sharing](/java-sdk/sharing): ovok.sharing() shares patient records: create and redeem invites(), list and revoke shares(), and read or change sharing settings(). - [Consent](/java-sdk/consent): ovok.consent() reads and records the signed-in user's legal() and medical() consent, and requests consent signatures. - [Locales](/java-sdk/locales): ovok.locales() reads and changes the project's locales. - [Localizations](/java-sdk/localizations): ovok.localizations() manages localization strings per language, and i18next() reads and writes a whole language as an i18next document. - [Email](/java-sdk/email): ovok.email() reads the project's email templates(). - [CMS](/java-sdk/cms): ovok.cms() reads CMS content. Every read includes the configured tenantCode. - [Content](/java-sdk/content): ovok.content() manages localized content entries by type, language and key, and copies entries from the parent project. - [Documents](/java-sdk/documents): ovok.documents() lists the project's documents, creates and replaces uploads, edits metadata, and builds download URLs. - [Questionnaires](/java-sdk/questionnaires): ovok.questionnaires() runs FHIR Questionnaire operations, such as $populate. - [Questionnaire responses](/java-sdk/questionnaire-responses): ovok.questionnaireResponses() submits QuestionnaireResponses, optionally with Observations in the same transaction, and extracts data from them. - [Video calls](/java-sdk/video-calls): ovok.videoCalls() runs LiveKit video calls: call appointments(), access() for users and guests, and invite emails(). - [AI](/java-sdk/ai): ovok.ai() calls the Ovok AI routes: chat() sessions and messages, translate, and fhirSearch. - [Wearables](/java-sdk/wearables): ovok.wearables() manages wearable providers() and the user's connections(): authorize, sync and remove. - [Bots](/java-sdk/bots): ovok.bots() runs an Ovok bot with an input. - [Health](/java-sdk/health): ovok.health() checks that the Ovok API is up. - [Compatibility](/java-sdk/compatibility): OvokClient - API reference - [API reference](/java-sdk/reference): Generated reference for every public class of the Java SDK. - [AccountsResource](/java-sdk/reference/accounts-resource): Lists Ovok accounts. - [AiResource](/java-sdk/reference/ai-resource): Calls the Ovok AI routes under /ai. - [AlarmsResource](/java-sdk/reference/alarms-resource): Reads and acknowledges device alarm notifications. - [AuthResource](/java-sdk/reference/auth-resource): Signs users in and out and manages their account. - [AuthSessionsResource](/java-sdk/reference/auth-sessions-resource): Lists and revokes the signed-in user's sessions. - [BotsResource](/java-sdk/reference/bots-resource): Runs Ovok bots. - [ChangeHistoryResource](/java-sdk/reference/change-history-resource): Reads the change history. - [ChildProjectsResource](/java-sdk/reference/child-projects-resource): Manages child projects of the current project. - [CmsResource](/java-sdk/reference/cms-resource): Reads published CMS content for the configured tenant. - [ConsentResource](/java-sdk/reference/consent-resource): Reads and records the signed-in user's consents and requests consent signatures. - [ContentResource](/java-sdk/reference/content-resource): Manages localized content entries in the project's CMS. - [DevicesResource](/java-sdk/reference/devices-resource): Manages Slim devices, their live rows and telemetry. - [DocumentsResource](/java-sdk/reference/documents-resource): Lists, uploads and links to the project's documents. - [EmailResource](/java-sdk/reference/email-resource): Reads project email settings. - [ExportsResource](/java-sdk/reference/exports-resource): Exports patient data. - [FhirResource](/java-sdk/reference/fhir-resource): Reads and writes FHIR R4 resources under the configured FHIR path. - [HealthResource](/java-sdk/reference/health-resource): Checks API health. - [InvitesResource](/java-sdk/reference/invites-resource): Creates and accepts invites. - [LocalesResource](/java-sdk/reference/locales-resource): Reads and changes the project locales. - [LocalizationsResource](/java-sdk/reference/localizations-resource): Manages localization strings. - [LocationsResource](/java-sdk/reference/locations-resource): Manages locations through the slim API. - [LoginCredentials](/java-sdk/reference/login-credentials): Email and password for AuthResource.login(LoginCredentials). - [LoginResult](/java-sdk/reference/login-result): Result of a sign-in. - [MeasurementsResource](/java-sdk/reference/measurements-resource): Builds, saves and reads measurement Observations. - [MedicalNotificationsResource](/java-sdk/reference/medical-notifications-resource): Reads and acknowledges medical notification episodes. - [MemoryStorage](/java-sdk/reference/memory-storage): In-memory OvokStorage; the session is lost when the process exits. - [MfaChallenge](/java-sdk/reference/mfa-challenge): Pending MFA sign-in, answered with AuthResource.verifyMfa(MfaChallenge, String). - [NotificationNotesResource](/java-sdk/reference/notification-notes-resource): Manages notes on notifications. - [NotificationsResource](/java-sdk/reference/notifications-resource): Reads Slim notifications and groups the alarm, out-of-bed, medical and note routes. - [OrganizationsResource](/java-sdk/reference/organizations-resource): Manages organizations through the slim API. - [OutOfBedResource](/java-sdk/reference/out-of-bed-resource): Reads out-of-bed notifications. - [OvokApiException](/java-sdk/reference/ovok-api-exception): Thrown when the Ovok API answers with a non-2xx status that has no more specific subclass. - [OvokAuthenticationException](/java-sdk/reference/ovok-authentication-exception): Thrown for HTTP 401: missing or invalid credentials. - [OvokClient](/java-sdk/reference/ovok-client): Minimal FHIR reader that uses a fixed access token. - [OvokConfigException](/java-sdk/reference/ovok-config-exception): Thrown when the SDK configuration or a call's inputs are invalid before any request is sent. - [OvokConfig](/java-sdk/reference/ovok-config): Settings for an Ovok client; build one with builder(). - [OvokConflictException](/java-sdk/reference/ovok-conflict-exception): Thrown for HTTP 409: the request conflicts with the current state. - [OvokConnectionException](/java-sdk/reference/ovok-connection-exception): Thrown when the SDK cannot reach the Ovok API or a paged read is interrupted. - [OvokException](/java-sdk/reference/ovok-exception): Base class of every exception the SDK throws; carries the API's error details when there is a response. - [OvokNotFoundException](/java-sdk/reference/ovok-not-found-exception): Thrown for HTTP 404: the resource does not exist. - [OvokPermissionException](/java-sdk/reference/ovok-permission-exception): Thrown for HTTP 403: signed in but not allowed. - [OvokRateLimitException](/java-sdk/reference/ovok-rate-limit-exception): Thrown for HTTP 429: rate limited; see retryAfter(). - [OvokServiceException](/java-sdk/reference/ovok-service-exception): Thrown for HTTP 5xx: the Ovok API failed. - [OvokStorage](/java-sdk/reference/ovok-storage): Key-value store that keeps the signed-in session between runs. - [OvokValidationException](/java-sdk/reference/ovok-validation-exception): Thrown for HTTP 400 and 422, and when the SDK rejects an invalid argument. - [Ovok](/java-sdk/reference/ovok): Entry point of the Ovok SDK; exposes one resource per API namespace. - [Page](/java-sdk/reference/page): One page of results that can iterate through every later page. - [PatientResource](/java-sdk/reference/patient-resource): Reads and writes the signed-in patient's FHIR records. - [PatientThresholdsResource](/java-sdk/reference/patient-thresholds-resource): Reads and sets one patient's vital thresholds. - [PatientsResource](/java-sdk/reference/patients-resource): Manages Slim patients. - [PoliciesResource](/java-sdk/reference/policies-resource): Manages access policies through the slim API. - [PractitionersResource](/java-sdk/reference/practitioners-resource): Invites practitioners. - [ProfileType](/java-sdk/reference/profile-type): FHIR profile type a user signs in as. - [ProjectsResource](/java-sdk/reference/projects-resource): Lists projects and manages the current project's members, features and settings. - [QuestionnaireResponsesResource](/java-sdk/reference/questionnaire-responses-resource): Submits QuestionnaireResponses and extracts data from them. - [QuestionnairesResource](/java-sdk/reference/questionnaires-resource): Runs FHIR Questionnaire operations. - [RegisterResult](/java-sdk/reference/register-result): Result of AuthResource.register(Map). - [RequestOptions](/java-sdk/reference/request-options): Per-request options. - [SaveObservationsOptions](/java-sdk/reference/save-observations-options): Options for saving observations through MeasurementsResource. - [Session](/java-sdk/reference/session): Signed-in user session. - [SharingResource](/java-sdk/reference/sharing-resource): Shares patient records through invites. - [SignalsResource](/java-sdk/reference/signals-resource): Enables Signals for patients and repairs undeliverable Signals config. - [SleepResource](/java-sdk/reference/sleep-resource): Reads sleep statistics. - [SlimProjectsResource](/java-sdk/reference/slim-projects-resource): Reads and updates Slim projects and their users. - [SlimResource](/java-sdk/reference/slim-resource): Groups the Slim care-home routes (/v1/slim) by resource. - [ThresholdsResource](/java-sdk/reference/thresholds-resource): Reads and sets the project vital thresholds. - [TrendsResource](/java-sdk/reference/trends-resource): Reads vital trend graphs. - [UnauthenticatedReason](/java-sdk/reference/unauthenticated-reason): Why the SDK signed the user out; passed to OvokConfig.Builder.onUnauthenticated(Consumer). - [UsersResource](/java-sdk/reference/users-resource): Deletes users. - [VideoCallsResource](/java-sdk/reference/video-calls-resource): Runs LiveKit video calls under /video-call/livekit. - [WearablesResource](/java-sdk/reference/wearables-resource): Manages wearable providers and connections under /v1/wearables. - [ZonesResource](/java-sdk/reference/zones-resource): Reads zones. - **Flutter SDK** - [Flutter SDK](/flutter-sdk): Call the Ovok API from Dart and Flutter apps. - [Get started](/flutter-sdk/getting-started): The SDK requires Dart 3.6 or later. Flutter apps can use ovok_flutter, which re-exports - [Flutter package](/flutter-sdk/flutter): What ovok_flutter adds over ovok_sdk, and how to set it up in a Flutter app. - [Authentication](/flutter-sdk/authentication): ovok.auth signs users in to the configured tenant and keeps their session in the - [Auth widgets](/flutter-sdk/auth-widgets): Sign-in, registration, password reset and account widgets for Flutter. - [FHIR and paging](/flutter-sdk/fhir): ovok.fhir reads and writes FHIR R4 resources as decoded JSON (JsonMap). - [Bluetooth devices](/flutter-sdk/bluetooth): Scan for supported BLE peripherals and collect their readings. - [Questionnaire forms](/flutter-sdk/questionnaire-forms): Render FHIR Questionnaires as validated Flutter forms. - [Realtime updates](/flutter-sdk/realtime): ovok.realtime.subscribe() creates a FHIR Subscription, opens its authenticated - [Measurements](/flutter-sdk/measurements): ovok.measurements turns device readings into FHIR Observations and saves them so a - [Background sync](/flutter-sdk/background-sync): Schedule secure offline measurement delivery on Android and iOS. - [Data sync](/flutter-sdk/data-sync): Display health data sync progress in a Flutter app. - [Health data sync](/flutter-sdk/health-sync): Read step counts and heart rate from Apple Health and Health Connect. - [Patient apps](/flutter-sdk/patient-apps): These namespaces serve apps used by a signed-in patient. - [Care platform](/flutter-sdk/care-platform): These namespaces serve care teams and administrators. - [Errors and retries](/flutter-sdk/errors-and-retries): Every failure is an OvokException with status, code, type, message, - API reference - [API reference](/flutter-sdk/reference): Generated reference for every public symbol of the Flutter SDK packages. - ovok_sdk - [ovok_sdk](/flutter-sdk/reference/ovok_sdk): Generated reference for ovok_sdk. - [AI resource](/flutter-sdk/reference/ovok_sdk/ai-resource): Generated reference for ovok_sdk: ai resource. - [Auth resource](/flutter-sdk/reference/ovok_sdk/auth-resource): Generated reference for ovok_sdk: auth resource. - [Auth sessions resource](/flutter-sdk/reference/ovok_sdk/auth-sessions-resource): Generated reference for ovok_sdk: auth sessions resource. - [Bots resource](/flutter-sdk/reference/ovok_sdk/bots-resource): Generated reference for ovok_sdk: bots resource. - [CMS resource](/flutter-sdk/reference/ovok_sdk/cms-resource): Generated reference for ovok_sdk: cms resource. - [Consent resource](/flutter-sdk/reference/ovok_sdk/consent-resource): Generated reference for ovok_sdk: consent resource. - [Content resource](/flutter-sdk/reference/ovok_sdk/content-resource): Generated reference for ovok_sdk: content resource. - [Documents resource](/flutter-sdk/reference/ovok_sdk/documents-resource): Generated reference for ovok_sdk: documents resource. - [FHIR resource](/flutter-sdk/reference/ovok_sdk/fhir-resource): Generated reference for ovok_sdk: fhir resource. - [Health resource](/flutter-sdk/reference/ovok_sdk/health-resource): Generated reference for ovok_sdk: health resource. - [Measurement types](/flutter-sdk/reference/ovok_sdk/measurement-types): Generated reference for ovok_sdk: measurement types. - [Measurements resource](/flutter-sdk/reference/ovok_sdk/measurements-resource): Generated reference for ovok_sdk: measurements resource. - [Offline measurement queue](/flutter-sdk/reference/ovok_sdk/offline-measurement-queue): Generated reference for ovok_sdk: offline measurement queue. - [Ovok exception](/flutter-sdk/reference/ovok_sdk/ovok-exception): Generated reference for ovok_sdk: ovok exception. - [Ovok secure store](/flutter-sdk/reference/ovok_sdk/ovok-secure-store): Generated reference for ovok_sdk: ovok secure store. - [Ovok](/flutter-sdk/reference/ovok_sdk/ovok): Generated reference for ovok_sdk: ovok. - [Page](/flutter-sdk/reference/ovok_sdk/page): Generated reference for ovok_sdk: page. - [Patch operation](/flutter-sdk/reference/ovok_sdk/patch-operation): Generated reference for ovok_sdk: patch operation. - [Patient resource](/flutter-sdk/reference/ovok_sdk/patient-resource): Generated reference for ovok_sdk: patient resource. - [Platform resources](/flutter-sdk/reference/ovok_sdk/platform-resources): Generated reference for ovok_sdk: platform resources. - [Questionnaires resource](/flutter-sdk/reference/ovok_sdk/questionnaires-resource): Generated reference for ovok_sdk: questionnaires resource. - [Realtime resource](/flutter-sdk/reference/ovok_sdk/realtime-resource): Generated reference for ovok_sdk: realtime resource. - [Registration](/flutter-sdk/reference/ovok_sdk/registration): Generated reference for ovok_sdk: registration. - [Resource types](/flutter-sdk/reference/ovok_sdk/resource-types): Generated reference for ovok_sdk: resource types. - [Session](/flutter-sdk/reference/ovok_sdk/session): Generated reference for ovok_sdk: session. - [Sign in results](/flutter-sdk/reference/ovok_sdk/sign-in-results): Generated reference for ovok_sdk: sign in results. - [Signals resource](/flutter-sdk/reference/ovok_sdk/signals-resource): Generated reference for ovok_sdk: signals resource. - [Slim resource](/flutter-sdk/reference/ovok_sdk/slim-resource): Generated reference for ovok_sdk: slim resource. - [URL](/flutter-sdk/reference/ovok_sdk/url): Generated reference for ovok_sdk: url. - [User session](/flutter-sdk/reference/ovok_sdk/user-session): Generated reference for ovok_sdk: user session. - [Video calls resource](/flutter-sdk/reference/ovok_sdk/video-calls-resource): Generated reference for ovok_sdk: video calls resource. - [Wearables resource](/flutter-sdk/reference/ovok_sdk/wearables-resource): Generated reference for ovok_sdk: wearables resource. - ovok_flutter - [ovok_flutter](/flutter-sdk/reference/ovok_flutter): Generated reference for ovok_flutter. - [Account actions](/flutter-sdk/reference/ovok_flutter/account-actions): Generated reference for ovok_flutter: account actions. - [Auth translations](/flutter-sdk/reference/ovok_flutter/auth-translations): Generated reference for ovok_flutter: auth translations. - [Background safe storage](/flutter-sdk/reference/ovok_flutter/background-safe-storage): Generated reference for ovok_flutter: background safe storage. - [Bluetooth device instructions](/flutter-sdk/reference/ovok_flutter/bluetooth-device-instructions): Generated reference for ovok_flutter: bluetooth device instructions. - [Bluetooth device manager](/flutter-sdk/reference/ovok_flutter/bluetooth-device-manager): Generated reference for ovok_flutter: bluetooth device manager. - [Bluetooth device picker](/flutter-sdk/reference/ovok_flutter/bluetooth-device-picker): Generated reference for ovok_flutter: bluetooth device picker. - [Bluetooth provider](/flutter-sdk/reference/ovok_flutter/bluetooth-provider): Generated reference for ovok_flutter: bluetooth provider. - [Built in devices](/flutter-sdk/reference/ovok_flutter/built-in-devices): Generated reference for ovok_flutter: built in devices. - [Driver](/flutter-sdk/reference/ovok_flutter/driver): Generated reference for ovok_flutter: driver. - [Errors](/flutter-sdk/reference/ovok_flutter/errors): Generated reference for ovok_flutter: errors. - [Flutter reactive ble scanner](/flutter-sdk/reference/ovok_flutter/flutter-reactive-ble-scanner): Generated reference for ovok_flutter: flutter reactive ble scanner. - [Health data mode](/flutter-sdk/reference/ovok_flutter/health-data-mode): Generated reference for ovok_flutter: health data mode. - [Health import provider](/flutter-sdk/reference/ovok_flutter/health-import-provider): Generated reference for ovok_flutter: health import provider. - [Health import](/flutter-sdk/reference/ovok_flutter/health-import): Generated reference for ovok_flutter: health import. - [Health sync configuration](/flutter-sdk/reference/ovok_flutter/health-sync-configuration): Generated reference for ovok_flutter: health sync configuration. - [Health sync mobile](/flutter-sdk/reference/ovok_flutter/health-sync-mobile): Generated reference for ovok_flutter: health sync mobile. - [Health sync types](/flutter-sdk/reference/ovok_flutter/health-sync-types): Generated reference for ovok_flutter: health sync types. - [Integrated devices](/flutter-sdk/reference/ovok_flutter/integrated-devices): Generated reference for ovok_flutter: integrated devices. - [Models](/flutter-sdk/reference/ovok_flutter/models): Generated reference for ovok_flutter: models. - [Notifications](/flutter-sdk/reference/ovok_flutter/notifications): Generated reference for ovok_flutter: notifications. - [Offline measurement background sync](/flutter-sdk/reference/ovok_flutter/offline-measurement-background-sync): Generated reference for ovok_flutter: offline measurement background sync. - [Os permissions](/flutter-sdk/reference/ovok_flutter/os-permissions): Generated reference for ovok_flutter: os permissions. - [Ports](/flutter-sdk/reference/ovok_flutter/ports): Generated reference for ovok_flutter: ports. - [Profile form](/flutter-sdk/reference/ovok_flutter/profile-form): Generated reference for ovok_flutter: profile form. - [Questionnaire form](/flutter-sdk/reference/ovok_flutter/questionnaire-form): Generated reference for ovok_flutter: questionnaire form. - [Reading sink](/flutter-sdk/reference/ovok_flutter/reading-sink): Generated reference for ovok_flutter: reading sink. - [Register](/flutter-sdk/reference/ovok_flutter/register): Generated reference for ovok_flutter: register. - [Reset password](/flutter-sdk/reference/ovok_flutter/reset-password): Generated reference for ovok_flutter: reset password. - [Result policy](/flutter-sdk/reference/ovok_flutter/result-policy): Generated reference for ovok_flutter: result policy. - [Runtime factory](/flutter-sdk/reference/ovok_flutter/runtime-factory): Generated reference for ovok_flutter: runtime factory. - [Runtime](/flutter-sdk/reference/ovok_flutter/runtime): Generated reference for ovok_flutter: runtime. - [Session list](/flutter-sdk/reference/ovok_flutter/session-list): Generated reference for ovok_flutter: session list. - [Sig profiles](/flutter-sdk/reference/ovok_flutter/sig-profiles): Generated reference for ovok_flutter: sig profiles. - [Sign in](/flutter-sdk/reference/ovok_flutter/sign-in): Generated reference for ovok_flutter: sign in. - [Sync progress list](/flutter-sdk/reference/ovok_flutter/sync-progress-list): Generated reference for ovok_flutter: sync progress list. - [Sync progress](/flutter-sdk/reference/ovok_flutter/sync-progress): Generated reference for ovok_flutter: sync progress. - Guides - **Guides** - [Guides](/guides): Step-by-step guides for building with OVOK. - Blood-pressure monitoring - [Monitor blood pressure at home](/guides/chf-remote-monitoring): Build a patient app that pairs a blood-pressure monitor and a scale over Bluetooth, saves every reading to Ovok, and shows the history. - [Step 1 · Create the React Native app](/guides/chf-remote-monitoring/create-the-react-native-app): Create an Expo project, install Ovok Native and its required peer packages, and configure Bluetooth native permissions. - [Step 2 · Create and configure the Ovok project](/guides/chf-remote-monitoring/create-and-configure-the-ovok-project): Set up a sandbox project, patient registration, login, AccessPolicy, and atomic measurement writes in Ovok Console. - [Step 3 · Configure localisation with Ovok CMS](/guides/chf-remote-monitoring/configure-localisation): Initialize i18next and load published app translations from Ovok CMS rather than bundling translation files. - [Step 4 · Choose connected devices](/guides/chf-remote-monitoring/choose-connected-devices): Select the Viatom BP2 and F4 / LeScale from the current React Native SDK catalog and verify their measurement capabilities. - [Step 5 · Initialise Ovok and Bluetooth](/guides/chf-remote-monitoring/initialise-ovok-and-bluetooth): Create one persisted Ovok client and mount the native Bluetooth runtime for the authenticated patient area. - [Step 6 · Build onboarding and authentication](/guides/chf-remote-monitoring/build-onboarding-and-authentication): Add patient sign-in, patient registration, and a protected app area using the native Ovok authentication components. - [Step 7 · Build the measurement screen](/guides/chf-remote-monitoring/build-the-measurement-screen): Save blood-pressure, ECG, and LeScale weight readings through Ovok and render recent values and ECG waveforms. - [Step 8 · Build the Devices section](/guides/chf-remote-monitoring/build-the-devices-section): Let patients discover, pair, review, and forget the two supported tutorial devices. - [Step 9 · Build Settings](/guides/chf-remote-monitoring/build-settings): Build an account settings screen with supported profile editing, language selection, device access, consent information, and sign-out. - [Step 10 · Complete localisation with Ovok CMS](/guides/chf-remote-monitoring/complete-localisation): Publish every patient-app and native-auth translation in Ovok CMS, then switch languages through i18next. - Medication Reminder - [Build a Medication Reminder App with Ovok in 30 Minutes](/guides/medication-reminder): A hands-on Expo and React Native tutorial for medication plans, local reminders, patient-reported adherence, and offline behavior with Ovok. - [Step 1 · Create the React Native app](/guides/medication-reminder/create-the-react-native-app): Set up Expo, the Ovok SDKs, local notifications, SQLCipher storage, and native builds for the medication reminder tutorial. - [Step 2 · Create and configure the Ovok project](/guides/medication-reminder/create-and-configure-the-ovok-project): Configure a sandbox project, patient access, medication resource permissions, transactions, and CMS translations for the tutorial app. - [Step 3 · Model the medication plan](/guides/medication-reminder/model-the-medication-plan): Represent a synthetic medication schedule with supported FHIR R4 resources and read it without inventing an Ovok medication abstraction. - [Step 4 · Set up localisation and time handling](/guides/medication-reminder/set-up-localisation-and-time-handling): Load interface strings from Ovok CMS and define how the app interprets FHIR wall-clock medication times across locales and timezones. - [Step 5 · Initialise Ovok and authentication](/guides/medication-reminder/initialise-ovok-and-authentication): Configure a persisted Ovok client, sign in through React Native SDK patient forms, and load the authenticated Patient's medication plan. - [Step 6 · Build the Today screen](/guides/medication-reminder/build-the-today-screen): Turn supported MedicationRequest timing into a patient-facing daily schedule with explicit upcoming, due, recorded, and app-derived missed states. - [Step 7 · Add medication reminders](/guides/medication-reminder/add-medication-reminders): Schedule, replace, and cancel privacy-conscious local medication reminders with Expo Notifications. - [Step 8 · Record medication adherence](/guides/medication-reminder/record-medication-adherence): Save patient-reported taken and skipped dose occurrences as supported FHIR MedicationAdministration resources. - [Step 9 · Build History and Medication Details](/guides/medication-reminder/build-history-and-medication-details): Show the source MedicationRequest and patient-reported MedicationAdministration history with paginated FHIR searches. - [Step 10 · Offline behavior, Settings, and localisation](/guides/medication-reminder/offline-settings-and-localisation): Add an encrypted app-owned FHIR cache and adherence outbox, then finish settings and CMS-backed English/German UI text. - Type 2 Diabetes Risk Screening - [Build a Type 2 Diabetes Risk Screening App with Ovok in 30 Minutes](/guides/type-2-diabetes): A ten-step Expo and React Native implementation guide for FINDRISC, FHIR questionnaires, TeleBGM Gen1 BLE, and blood-glucose Observations. - [Step 1 · Create the React Native app](/guides/type-2-diabetes/create-the-react-native-app): Create an Expo Router app with current Ovok packages, native Bluetooth permissions, and a reproducible development build. - [Step 2 · Create and configure the Ovok project](/guides/type-2-diabetes/create-and-configure-the-ovok-project): Configure a sandbox project for patient registration, least-privilege Questionnaire access, QuestionnaireResponse writes, and glucose Observations. - [Step 3 · Configure localisation with Ovok CMS](/guides/type-2-diabetes/configure-localisation): Initialize i18next and load the app's published interface strings from Ovok CMS, keeping clinical Questionnaire content separate. - [Step 4 · Choose the glucose device](/guides/type-2-diabetes/choose-the-glucose-device): Verify TeleBGM Gen1 BLE in the current supported-device catalogue and use its Ovok runtime declaration. - [Step 5 · Add the FINDRISC Questionnaire](/guides/type-2-diabetes/add-the-findrisc-questionnaire): Load a licensed FHIR Questionnaire, render its answers as QuestionnaireResponse, and verify an app-owned scoring map with boundary tests. - [Step 6 · Initialise Ovok, authentication, and Bluetooth](/guides/type-2-diabetes/initialise-ovok-auth-and-bluetooth): Create one persistent OvokClient, compose providers, protect Patient routes, and start one authenticated TeleBGM Bluetooth runtime. - [Step 7 · Build the screening home and Questionnaire flow](/guides/type-2-diabetes/build-the-screening-home): Build an independent FINDRISC assessment card and glucose card, render the approved FHIR Questionnaire, and persist its response. - [Step 8 · Connect TeleBGM and save glucose readings](/guides/type-2-diabetes/connect-telebgm-and-save-readings): Discover and pair TeleBGM Gen1 BLE, receive normalized mg/dL readings, and save them through Ovok as FHIR Observations. - [Step 9 · Build screening results and history](/guides/type-2-diabetes/build-results-and-history): Read Patient-scoped QuestionnaireResponses and blood-glucose Observations, preserving the distinction between a risk assessment and a measurement. - [Step 10 · Settings, localisation, and validation](/guides/type-2-diabetes/settings-localisation-and-validation): Add a Patient settings screen, persist the UI language, finish CMS translations, and verify the complete sandbox journey. - Whole-Person Healthspan & Coaching - [Build a Whole-Person Healthspan & Coaching App with Ovok](/guides/whole-person-wellness): Build a React Native coaching app with Ovok authentication, structured wellness reflections, FHIR data and a human coach. - [Create the React Native app](/guides/whole-person-wellness/create-the-react-native-app): Create the Expo project, install Ovok packages and configure native health-platform permissions. - [Create and configure the Ovok project](/guides/whole-person-wellness/create-and-configure-the-ovok-project): Configure a sandbox project, patient registration and least-privilege access for the tutorial. - [Build onboarding, authentication and profile](/guides/whole-person-wellness/build-onboarding-authentication-and-profile): Build patient registration and sign-in with Ovok Native, then load the authenticated Patient profile. - [Connect Apple Health and Health Connect](/guides/whole-person-wellness/connect-apple-health-and-health-connect): Request optional, read-only health access and import supported sleep, step-count and resting-heart-rate records. - [Build the five-domain wellness model](/guides/whole-person-wellness/build-the-five-domain-wellness-model): Separate imported measurements from sleep, movement, nutrition, stress and recovery reflections. - [Build the weekly wellness reflection](/guides/whole-person-wellness/build-weekly-wellness-reflection): Create a FHIR Questionnaire for a short non-clinical weekly check-in and save responses with QuestionnaireForm. - [Build the healthspan dashboard and trends](/guides/whole-person-wellness/build-the-healthspan-dashboard-and-trends): Render reflections and supported measurements as separate, descriptive trends. - [Add the coach relationship](/guides/whole-person-wellness/add-the-coach-relationship): Represent a coach with FHIR resources and use Ovok's server-enforced practitioner-sharing flow. - [Add goals and progress reviews](/guides/whole-person-wellness/add-goals-and-progress-reviews): Store member-chosen goals as FHIR Goal resources and review them with a coach. - [Finish history, settings, localisation and validation](/guides/whole-person-wellness/finish-history-settings-localisation-and-validation): Complete longitudinal history, health-data settings, Ovok CMS translations and end-to-end checks. - API reference - [API reference](/api): Every public route of the Ovok API, with its parameters, responses, errors and a request you can try. - **Ovok API** - Accounts - [Fetch data of existing accounts](/api/accounts/fetch-data-of-existing-accounts): Lists the email addresses of every account in the caller's project. Use it to build a participant picker, for example for a video call. **Auth:** bearer token. The caller must be a Practitioner, a project admin or a super admin, or hold an access policy named "System Owner". Other callers get 403. **Scope:** the project of the token. ## Behaviour - Every project membership with a Patient or Practitioner profile and a User is included. Accounts whose User has no email are left out. - The handler returns only the `email` of each account. - The list is not paginated and has no defined sort order. ## Example ```bash curl -X GET 'https://api.sandbox.ovok.com/accounts' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' ``` - AI Translation - [Translate text](/api/ai-translation/translate-text): Translates a list of strings from one language to another with an LLM. Use it to draft translations for content and localization keys. **Auth:** bearer token. **Scope:** the caller's project (from the token). The project's settings can add instructions to the translation prompt. ## Behaviour - `sourceLanguage` and `targetLanguage` take a language code such as `en` or `de-DE`, or a language name. Each is 2 to 100 characters. - `text` holds 1 or more strings. Each string and all strings together are limited to 10,000 characters. - Strings can be plain text, HTML or Markdown, and can contain placeholders such as `{{name}}`. Markup and placeholders are meant to be kept as they are, but the output comes from an LLM, so check it before you publish it. - The response returns the translations in `text`, in the same order as the input. - If the project has a setting named `TRANSLATION_ADDITIONAL_PROMPT`, its value is added to the prompt for every translation in that project. - Nothing is stored. The route only returns the translation. ## Example ```bash curl -X POST 'https://api.sandbox.ovok.com/ai/translation' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \ -H 'Content-Type: application/json' \ -d '{ "sourceLanguage": "en", "targetLanguage": "de", "text": ["Hello {{name}}, how are you?", "
Your appointment is today.
"] }' ``` - Authentication - [Change user password](/api/authentication/change-user-password): Changes the password of the signed-in user. Use it from an account settings screen where the user knows their current password. **Auth:** bearer token. **Scope:** the User of the token. ## Behaviour - `oldPassword` must match the current password. A wrong `oldPassword`, or an account with no password set, gives 401. - The new password may equal the old one. - The password must have at least 12 characters, not counting spaces at either end, and must not be on Ovok's list of common and breached passwords. Any character is allowed and none is required. A refused password answers 422 with the error on `newPassword`. - The route does not end existing sessions. - The email address is not marked as verified by this route. - The response is the updated `User` without its password hash. ## Example ```bash curl -X PATCH 'https://api.sandbox.ovok.com/v2/auth/change-password' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \ -H 'Content-Type: application/json' \ -d '{ "oldPassword": "Ex4mple-Passw0rd", "newPassword": "a-new-password" }' ``` - [Refresh access token](/api/authentication/refresh-access-token): Exchanges a refresh token for a new access token and a new refresh token. Call it before the access token expires to keep a session alive. **Auth:** none. The refresh token in the body is the credential. ## Behaviour - Ovok forwards the token to Medplum's `oauth2/token` endpoint (`grant_type=refresh_token`, scope `openid offline_access`). - The request body takes the token as `refresh_token`. - The response holds `accessToken`, `refreshToken` and `expiresIn` (seconds), the same names the login routes use. Store the new `refreshToken`: use it for the next refresh. - The response also carries the same three values as `access_token`, `refresh_token` and `expires_in`. Those names are deprecated and will be removed in a later release: read the camelCase ones. - A refresh token Medplum refuses (spent, expired, revoked, or not a refresh token at all) gives a 401 with Medplum's error description. A refresh token is single-use, so retrying it after a successful refresh also gives a 401: sign in again. - A body without `refresh_token` gives a 400. - A session of a Patient whose project turned `PATIENT_LOGIN_ENABLED` off, or of a Practitioner whose project turned `PRACTITIONER_LOGIN_ENABLED` off, gives a 403 `Login is not enabled for this project.` That ends the session: the refresh token is used up, and signing in again is refused while login is off. A Practitioner who is a project admin is not refused, so an admin who is signed in can still turn login back on (they cannot sign in again while it is off). The access token already issued lasts until it expires (60 minutes). - `POST /oauth2/token` applies the same check to every token it grants (refresh and code exchange), answering `403 { "error": "access_denied", "error_description": "Login is not enabled for this project." }`. ## Example ```bash curl -X POST 'https://api.sandbox.ovok.com/auth/refresh-token' \ -H 'Content-Type: application/json' \ -d '{ "refresh_token": "example.jwt.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk" }' ``` - [Log out (server-side token revocation)](/api/authentication/log-out-server-side-token-revocation): Signs the caller out on the server: the access token in the `Authorization` header stops working for every authenticated route, although it has not expired. **Auth:** bearer token. A missing or invalid token gives 401. ## Behaviour - Ovok adds the token to a denylist. The entry lasts for the rest of the token's lifetime (its `exp` claim), so the list never outgrows the set of live tokens. - Only this access token is revoked. The refresh token is not touched here; to end other sessions use `DELETE /auth/session/{option}`. - The call is idempotent: repeating it with the same token returns 204 with no further effect. A token without an `exp` claim, or one that has already expired, is ignored. - If the denylist store is unavailable the call still returns 204, and the token stays valid until it expires. Clear the local session as well. - The response has no body (204 No Content). - The route is available at both `/auth/logout` and `/v1/auth/logout`. ## Example ```bash curl -X POST 'https://api.sandbox.ovok.com/v1/auth/logout' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' ``` - [Log out (server-side token revocation)](/api/authentication/log-out-server-side-token-revocation-2): Signs the caller out on the server: the access token in the `Authorization` header stops working for every authenticated route, although it has not expired. **Auth:** bearer token. A missing or invalid token gives 401. ## Behaviour - Ovok adds the token to a denylist. The entry lasts for the rest of the token's lifetime (its `exp` claim), so the list never outgrows the set of live tokens. - Only this access token is revoked. The refresh token is not touched here; to end other sessions use `DELETE /auth/session/{option}`. - The call is idempotent: repeating it with the same token returns 204 with no further effect. A token without an `exp` claim, or one that has already expired, is ignored. - If the denylist store is unavailable the call still returns 204, and the token stays valid until it expires. Clear the local session as well. - The response has no body (204 No Content). - The route is available at both `/auth/logout` and `/v1/auth/logout`. ## Example ```bash curl -X POST 'https://api.sandbox.ovok.com/v1/auth/logout' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' ``` - [Schedule user account deletion](/api/authentication/schedule-user-account-deletion): Schedules deletion of the caller's own account. The account is not deleted right away: it stays until the scheduled date, and signing in again before then cancels the deletion. **Auth:** bearer token of the user who deletes their own account. **Scope:** the caller's own User, in the project of the token. ## Behaviour - `days` (0 to 90, default 30) sets how many days from now the account is due. `wipe` (default `false`) is recorded on the request. - Ovok signs the user out of all sessions, then records the deletion date on the User. Calling the route again resets the date. - A job runs every day at midnight. For each User whose date has passed, it deletes the project membership, the profile (Patient or Practitioner) and the User. - The job does not purge medical data yet, so `wipe` has no effect on what is deleted. - Ovok sends a confirmation email (`PATIENT_ACCOUNT_DELETION` for a Patient, `ACCOUNT_DELETION` otherwise). A failed email does not fail the request. - A successful login on the account (`POST /v2/auth/login`, or the tenant login routes) removes the scheduled deletion. - The response holds `scheduled` (the due date, ISO 8601) and the updated `user`, without its password hash. ## Example ```bash curl -X DELETE 'https://api.sandbox.ovok.com/auth/delete' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \ -H 'Content-Type: application/json' \ -d '{ "days": 30, "wipe": false }' ``` - [Login](/api/authentication/login): Signs a user in with email and password for the project that owns a client application, and returns an access token and a refresh token. New integrations should use the tenant login routes (`/auth/tenant/Patient/login/start` and `/auth/tenant/Practitioner/login/start`) instead. **Auth:** none. The email and password are the credential, and `clientId` names the project. **Scope:** the project that owns the `ClientApplication` with the given `clientId`. ## Behaviour - Ovok looks up the account by email in the client application's project. If a Patient and a Practitioner share the email in that project, one of them is picked and which one is not defined. - The account type's login switch must be on: the project setting `PATIENT_LOGIN_ENABLED` or `PRACTITIONER_LOGIN_ENABLED`. Both default to on. It is checked once the password is right, so its 403 never tells which emails have an account. - A wrong password and an unknown email give the same 401 `Invalid email or password.`, so the response does not tell you which one was wrong. An unknown `clientId` gives a 400. - After 5 failed sign-ins for one email within 15 minutes, that email's sign-ins answer 429 with `Retry-After` for 15 minutes, the right password included, whether or not the email has an account. A successful sign-in resets the count. - If Medplum's own login rate limit is hit, you get a 429 instead of a 401. Wait and retry. - Callers without a token may send at most 10 requests per minute from one IP address. - A successful login cancels a pending account deletion (`DELETE /auth/delete`). - This route has no MFA step. Use the tenant login routes for users with MFA enrolled. - The access token lasts one hour (`expiresIn` is in seconds). Use `POST /auth/refresh-token` to renew it. ## Example ```bash curl -X POST 'https://api.sandbox.ovok.com/v2/auth/login' \ -H 'Content-Type: application/json' \ -d '{ "email": "max.mustermann@example.com", "password": "Ex4mple-Passw0rd", "clientId": "c3499c27-2973-0a7f-807e-fb8676a92dcb" }' ``` - [Get account information](/api/authentication/get-account-information): Returns the signed-in user's context: project, membership, profile, user configuration, access policy and security details such as MFA status and sessions. Call it after sign-in to learn who the token belongs to. **Auth:** bearer token. A missing, non-bearer or invalid token gives 401. **Scope:** the project of the token. ## Behaviour - The answer is Medplum's `auth/me` for the token's session. - The call always discards Ovok's cached copy of this session's profile and reads a fresh one, so changes to the profile or access policy show up in the response and in later requests with the same token. - Use the returned `profile.resourceType` to tell Patients from Practitioners. ## Example ```bash curl -X GET 'https://api.sandbox.ovok.com/auth/me' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' ``` - Authentication - Session Management - [Get all sessions](/api/authentication---session-management/get-all-sessions): Lists the caller's live sessions (one per sign-in). Use it to build a "where you are signed in" screen. **Auth:** bearer token. **Scope:** the signed-in user's own sessions. ## Behaviour - A session is listed when its Login belongs to the user, has a project membership and is not revoked. Sessions that have been idle for a long time are listed as long as they are not revoked. - The list is ordered newest first. - `browser` and `os` are `null` for sessions that Medplum does not report them for. - The call only reads. It does not change any session. ## Example ```bash curl -X GET 'https://api.sandbox.ovok.com/auth/session' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' ``` - [Revoke sessions](/api/authentication---session-management/revoke-sessions): Signs the caller out of one or more sessions. Use it for "sign out everywhere" and "sign out other devices" actions. **Auth:** bearer token. **Scope:** the signed-in user's own sessions. A session ID that is not one of the caller's sessions revokes nothing. ## Behaviour - `option` in the path is one of: - `current`: the session of the token you call with. - `other`: every session except the current one. - `all`: every session, including the current one. - a session ID (UUID) from `GET /auth/session`: that one session. - Ovok rejects the access tokens of the revoked sessions on their next request, and marks each Login as revoked in Medplum so the session cannot be renewed. - Revoking `current` or `all` also ends the token you called with. - The response echoes the revoked sessions with `revoked: true`: one object for `current` or a session ID, a list for `other` and `all`. - To end only the token you hold, without the session, use `POST /v1/auth/logout`. ## Example ```bash curl -X DELETE 'https://api.sandbox.ovok.com/auth/session/other' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' ``` - Bots - Execute a custom script - [Execute a bot](/api/bots---execute-a-custom-script/execute-a-bot): Runs a Medplum Bot of your project, found by its identifier, and returns its output. Use it to call custom server-side logic without knowing the Bot's id. **Auth:** bearer token. The Bot is searched and run with your token, so you need read access to the Bot and permission to execute it. ## Behaviour - `identifier` uses FHIR token search syntax: `system|value`, `system|`, or just `value`. Exactly one Bot must match. - The JSON request body is passed to the Bot as its input, unchanged. The body is optional. - The response is whatever the Bot returns. ## Example ```bash curl -X POST 'https://api.sandbox.ovok.com/bots?identifier=https://example.com|get-users' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \ -H 'Content-Type: application/json' \ -d '{ "parameter1": "value1" }' ``` - CMS (FHIR Legacy) - [List content with translations](/api/cms-\(fhir-legacy\)/list-content-with-translations): Lists content items as pairs of the default-language item and its translation in the requested language. Use it for translation screens that show the original next to the translation. **Auth:** bearer token. **Scope:** the caller's project (from the token). Only items in that project are listed. ## Behaviour - `type` is one of `blog`, `video`, `audio`, `template` or `export-template`. - `language` is the target language and must be one of the languages in your project's locale settings. - Each result has `source` (the item in the project's default language) and `target` (the item in `language`) for the same key. Either one is missing when that language has no item for the key. - Pairs are sorted by the target's `date`, newest first, falling back to the source's `date`. - `search` is a fuzzy match over the title, author name, category, section titles and text, and key of both items. - `code` keeps only items with that content code. - `_count` defaults to `10` and `_offset` to `0`. `total` is the number of matching pairs before paging. - The route is also served under `/internal/cms/{type}`. ## Example ```bash curl -X GET 'https://api.sandbox.ovok.com/cms/blog/map/de-DE?search=sleep&_count=20' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' ``` - [List content with translations](/api/cms-\(fhir-legacy\)/list-content-with-translations-2): Lists content items as pairs of the default-language item and its translation in the requested language. Use it for translation screens that show the original next to the translation. **Auth:** bearer token. **Scope:** the caller's project (from the token). Only items in that project are listed. ## Behaviour - `type` is one of `blog`, `video`, `audio`, `template` or `export-template`. - `language` is the target language and must be one of the languages in your project's locale settings. - Each result has `source` (the item in the project's default language) and `target` (the item in `language`) for the same key. Either one is missing when that language has no item for the key. - Pairs are sorted by the target's `date`, newest first, falling back to the source's `date`. - `search` is a fuzzy match over the title, author name, category, section titles and text, and key of both items. - `code` keeps only items with that content code. - `_count` defaults to `10` and `_offset` to `0`. `total` is the number of matching pairs before paging. - The route is also served under `/internal/cms/{type}`. ## Example ```bash curl -X GET 'https://api.sandbox.ovok.com/cms/blog/map/de-DE?search=sleep&_count=20' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' ``` - [Copy all content from the parent project](/api/cms-\(fhir-legacy\)/copy-all-content-from-the-parent-project): Copies all content items of one type from the parent project, giving each copy a new key. Use it in a sub-project to start from the parent's content. **Auth:** bearer token. You must be a project admin. **Scope:** the caller's project (from the token) and its parent project. ## Behaviour - `type` is one of `blog`, `video`, `audio`, `template` or `export-template`. - `language` must be one of the languages in your project's locale settings. - If `language` is your project's default language, items in every language are copied. Otherwise only items in `language` are copied. - Each copy is a new item with a new key: the original key plus a random suffix. Translations of the same original key share the same new key. `date` is set to now and `author` to your profile. - Running the route again creates another set of copies. It does not update earlier copies. - If the parent has no matching items, nothing is copied and the response is still `{ "success": true }`. - The route is also served under `/internal/cms/{type}`. ## Example ```bash curl -X POST 'https://api.sandbox.ovok.com/cms/blog/en-US/copy-from-parent-project' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' ``` - [Copy all content from the parent project](/api/cms-\(fhir-legacy\)/copy-all-content-from-the-parent-project-2): Copies all content items of one type from the parent project, giving each copy a new key. Use it in a sub-project to start from the parent's content. **Auth:** bearer token. You must be a project admin. **Scope:** the caller's project (from the token) and its parent project. ## Behaviour - `type` is one of `blog`, `video`, `audio`, `template` or `export-template`. - `language` must be one of the languages in your project's locale settings. - If `language` is your project's default language, items in every language are copied. Otherwise only items in `language` are copied. - Each copy is a new item with a new key: the original key plus a random suffix. Translations of the same original key share the same new key. `date` is set to now and `author` to your profile. - Running the route again creates another set of copies. It does not update earlier copies. - If the parent has no matching items, nothing is copied and the response is still `{ "success": true }`. - The route is also served under `/internal/cms/{type}`. ## Example ```bash curl -X POST 'https://api.sandbox.ovok.com/cms/blog/en-US/copy-from-parent-project' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' ``` - [Copy one content item from the parent project](/api/cms-\(fhir-legacy\)/copy-one-content-item-from-the-parent-project): Copies one content item, by key, from the parent project, giving the copy a new key. **Auth:** bearer token. You must be a project admin. **Scope:** the caller's project (from the token) and its parent project. ## Behaviour - `type` is one of `blog`, `video`, `audio`, `template` or `export-template`. - `key` is the key of the item in the parent project. - If `language` is your project's default language, the item is copied in every language it exists in. Otherwise only the item in `language` is copied. - The copy gets a new key: the original key plus a random suffix. All copied languages share that new key. `date` is set to now and `author` to your profile. - Running the route again creates another copy. It does not update an earlier copy. - The response is `{ "success": true }`. - The route is also served under `/internal/cms/{type}`. ## Example ```bash curl -X POST 'https://api.sandbox.ovok.com/cms/blog/en-US/blog-sleeping-well-a1b2c3d4/copy-from-parent-project' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' ``` - [Copy one content item from the parent project](/api/cms-\(fhir-legacy\)/copy-one-content-item-from-the-parent-project-2): Copies one content item, by key, from the parent project, giving the copy a new key. **Auth:** bearer token. You must be a project admin. **Scope:** the caller's project (from the token) and its parent project. ## Behaviour - `type` is one of `blog`, `video`, `audio`, `template` or `export-template`. - `key` is the key of the item in the parent project. - If `language` is your project's default language, the item is copied in every language it exists in. Otherwise only the item in `language` is copied. - The copy gets a new key: the original key plus a random suffix. All copied languages share that new key. `date` is set to now and `author` to your profile. - Running the route again creates another copy. It does not update an earlier copy. - The response is `{ "success": true }`. - The route is also served under `/internal/cms/{type}`. ## Example ```bash curl -X POST 'https://api.sandbox.ovok.com/cms/blog/en-US/blog-sleeping-well-a1b2c3d4/copy-from-parent-project' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' ``` - [List content](/api/cms-\(fhir-legacy\)/list-content): Lists content items of one type in one language, with optional fuzzy search and paging. **Auth:** bearer token. **Scope:** the caller's project (from the token). Only items in that project are listed. ## Behaviour - `type` is one of `blog`, `video`, `audio`, `template` or `export-template`. - `language` must be one of the languages in your project's locale settings. - Items are sorted by `date`, newest first. - `search` is a fuzzy match over title, author name, category, section titles and text, and key. - `code` keeps only items with that content code. - `_count` defaults to `10` and `_offset` to `0`. `total` is the number of matching items before paging. - At most 10,000 items per type and language are considered. - The route is also served under `/internal/cms/{type}`. ## Example ```bash curl -X GET 'https://api.sandbox.ovok.com/cms/blog/en-US?search=sleep&_count=20&_offset=0' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' ``` - [Create content](/api/cms-\(fhir-legacy\)/create-content): Creates a content item of one type in one language. Use it to add a blog post, video, audio item or template to your project's content. **Auth:** bearer token. **Scope:** the caller's project (from the token). The item is stored in that project. ## Behaviour - `type` is one of `blog`, `video`, `audio`, `template` or `export-template`. - `language` must be one of the languages in your project's locale settings. - The item is stored as a FHIR `Composition`. Ovok sets `key`, `date` (now), `author` (your profile), `type`, `language` and the project. You cannot set them yourself. - `key` is built from the type and the title plus a random suffix. Use it to address the item in other routes and to link its translations across languages. - When you create an item in the project's default language, a background job creates an AI translation of it for every other project language. The translations appear later, not in this response. - The route is also served under `/internal/cms/{type}`. ## Example ```bash curl -X POST 'https://api.sandbox.ovok.com/cms/blog/en-US' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \ -H 'Content-Type: application/json' \ -d '{ "title": "Sleeping well", "category": "health", "section": [ { "title": "Introduction", "text": "Why sleep matters.", "code": ["main-content"] } ] }' ``` - [List content](/api/cms-\(fhir-legacy\)/list-content-2): Lists content items of one type in one language, with optional fuzzy search and paging. **Auth:** bearer token. **Scope:** the caller's project (from the token). Only items in that project are listed. ## Behaviour - `type` is one of `blog`, `video`, `audio`, `template` or `export-template`. - `language` must be one of the languages in your project's locale settings. - Items are sorted by `date`, newest first. - `search` is a fuzzy match over title, author name, category, section titles and text, and key. - `code` keeps only items with that content code. - `_count` defaults to `10` and `_offset` to `0`. `total` is the number of matching items before paging. - At most 10,000 items per type and language are considered. - The route is also served under `/internal/cms/{type}`. ## Example ```bash curl -X GET 'https://api.sandbox.ovok.com/cms/blog/en-US?search=sleep&_count=20&_offset=0' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' ``` - [Create content](/api/cms-\(fhir-legacy\)/create-content-2): Creates a content item of one type in one language. Use it to add a blog post, video, audio item or template to your project's content. **Auth:** bearer token. **Scope:** the caller's project (from the token). The item is stored in that project. ## Behaviour - `type` is one of `blog`, `video`, `audio`, `template` or `export-template`. - `language` must be one of the languages in your project's locale settings. - The item is stored as a FHIR `Composition`. Ovok sets `key`, `date` (now), `author` (your profile), `type`, `language` and the project. You cannot set them yourself. - `key` is built from the type and the title plus a random suffix. Use it to address the item in other routes and to link its translations across languages. - When you create an item in the project's default language, a background job creates an AI translation of it for every other project language. The translations appear later, not in this response. - The route is also served under `/internal/cms/{type}`. ## Example ```bash curl -X POST 'https://api.sandbox.ovok.com/cms/blog/en-US' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \ -H 'Content-Type: application/json' \ -d '{ "title": "Sleeping well", "category": "health", "section": [ { "title": "Introduction", "text": "Why sleep matters.", "code": ["main-content"] } ] }' ``` - [Get content by key and language](/api/cms-\(fhir-legacy\)/get-content-by-key-and-language): Returns one content item by type, language and key. **Auth:** bearer token. **Scope:** the caller's project (from the token). Only items in that project are found. ## Behaviour - `type` is one of `blog`, `video`, `audio`, `template` or `export-template`. - `language` must be one of the languages in your project's locale settings. - The route is also served under `/internal/cms/{type}`. ## Example ```bash curl -X GET 'https://api.sandbox.ovok.com/cms/blog/en-US/blog-sleeping-well-a1b2c3d4' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' ``` - [Delete content](/api/cms-\(fhir-legacy\)/delete-content): Deletes a content item, or all its translations when you name the default language. **Auth:** bearer token. **Scope:** the caller's project (from the token). Only items in that project are deleted. ## Behaviour - `type` is one of `blog`, `video`, `audio`, `template` or `export-template`. - If `language` is the project's default language, the item is deleted in every project language. Otherwise only that language's item is deleted. - Deleting a key that does not exist succeeds and changes nothing. - The response has no body. - The route is also served under `/internal/cms/{type}`. ## Example ```bash curl -X DELETE 'https://api.sandbox.ovok.com/cms/blog/en-US/blog-sleeping-well-a1b2c3d4' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' ``` - [Update content](/api/cms-\(fhir-legacy\)/update-content): Replaces the content item with the given key in one language. Use it to edit an item you created with `POST /cms/{type}/{language}`. **Auth:** bearer token. **Scope:** the caller's project (from the token). Only items in that project are found. ## Behaviour - `type` is one of `blog`, `video`, `audio`, `template` or `export-template`. - `language` must be one of the languages in your project's locale settings. - The body replaces `title`, `category`, `section` and `code`. Ovok sets `date` to now and `author` to your profile. - If more than one item exists for this type, key and language, Ovok keeps the first and deletes the others. - When you update an item, a background job runs for every project language. It only creates translations that do not exist yet; existing translations are not changed. - The route is also served under `/internal/cms/{type}`. ## Example ```bash curl -X PUT 'https://api.sandbox.ovok.com/cms/blog/en-US/blog-sleeping-well-a1b2c3d4' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \ -H 'Content-Type: application/json' \ -d '{ "title": "Sleeping well", "category": "health", "section": [ { "title": "Introduction", "text": "Why good sleep matters.", "code": ["main-content"] } ] }' ``` - [Get content by key and language](/api/cms-\(fhir-legacy\)/get-content-by-key-and-language-2): Returns one content item by type, language and key. **Auth:** bearer token. **Scope:** the caller's project (from the token). Only items in that project are found. ## Behaviour - `type` is one of `blog`, `video`, `audio`, `template` or `export-template`. - `language` must be one of the languages in your project's locale settings. - The route is also served under `/internal/cms/{type}`. ## Example ```bash curl -X GET 'https://api.sandbox.ovok.com/cms/blog/en-US/blog-sleeping-well-a1b2c3d4' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' ``` - [Delete content](/api/cms-\(fhir-legacy\)/delete-content-2): Deletes a content item, or all its translations when you name the default language. **Auth:** bearer token. **Scope:** the caller's project (from the token). Only items in that project are deleted. ## Behaviour - `type` is one of `blog`, `video`, `audio`, `template` or `export-template`. - If `language` is the project's default language, the item is deleted in every project language. Otherwise only that language's item is deleted. - Deleting a key that does not exist succeeds and changes nothing. - The response has no body. - The route is also served under `/internal/cms/{type}`. ## Example ```bash curl -X DELETE 'https://api.sandbox.ovok.com/cms/blog/en-US/blog-sleeping-well-a1b2c3d4' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' ``` - [Update content](/api/cms-\(fhir-legacy\)/update-content-2): Replaces the content item with the given key in one language. Use it to edit an item you created with `POST /cms/{type}/{language}`. **Auth:** bearer token. **Scope:** the caller's project (from the token). Only items in that project are found. ## Behaviour - `type` is one of `blog`, `video`, `audio`, `template` or `export-template`. - `language` must be one of the languages in your project's locale settings. - The body replaces `title`, `category`, `section` and `code`. Ovok sets `date` to now and `author` to your profile. - If more than one item exists for this type, key and language, Ovok keeps the first and deletes the others. - When you update an item, a background job runs for every project language. It only creates translations that do not exist yet; existing translations are not changed. - The route is also served under `/internal/cms/{type}`. ## Example ```bash curl -X PUT 'https://api.sandbox.ovok.com/cms/blog/en-US/blog-sleeping-well-a1b2c3d4' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \ -H 'Content-Type: application/json' \ -d '{ "title": "Sleeping well", "category": "health", "section": [ { "title": "Introduction", "text": "Why good sleep matters.", "code": ["main-content"] } ] }' ``` - Consent Signature - [Request consent signature](/api/consent-signature/request-consent-signature): Requests an e-signature for a FHIR `Consent`: Ovok renders the consent text as a PDF, uploads it to the e-signature service (Documenso) with the verifier as the signer, and returns the document metadata. **Auth:** bearer token. Every FHIR read runs with your token, so you must be able to read the Consent and the resources it points to. ## Behaviour - The Consent must have: - `patient` referencing a `Patient`; - a `provision.data` reference to a `Composition` that holds the consent text; - a `verification` entry whose `verifiedWith` references a `Patient` or `RelatedPerson`, with `verified` not `true`. - The text comes from the Composition section coded `main-content`. It is a Handlebars template filled with `patient`, `signer` and `organization` (the display names of the patient, the verifier and the project). - The signer's email is the first `email` entry in the verifier's `telecom`. - The document language comes from the Composition's CMS language coding when it is one of `en`, `de`, `fr`, `es`, `it`, `pl`; otherwise `de`. - On success an identifier with system `https://app.documenso.com` and the new document ID is added to the Consent. Each call creates a new signature document and adds another identifier. - The response holds `numberOfPages`, `title`, `author`, `subject`, `documentId` and `email` (the signer's address). ## Example ```bash curl -X POST 'https://api.sandbox.ovok.com/consent/signature/c3499c27-2973-0a7f-807e-fb8676a92dcb' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' ``` - Document - [Search documents](/api/document/search-documents): Lists the documents you can read, newest first by default. Use it to build a file list. **Auth:** bearer token. The search runs with your token, so Medplum returns only DocumentReferences you can read. ## Behaviour - `_count` sets the page size (default `100`), `_offset` the number of documents to skip (default `0`), and `_sort` the order (default `-_lastUpdated`). - `total` is an estimate from Medplum, not an exact count. Use it for a page counter, not for logic that needs the exact number. - Each entry has `id`, `meta.lastUpdated`, `author`, `fileName`, `contentType`, and `publicToken` when the document is public. Entries never include `uploadOptions`. ## Example ```bash curl -G 'https://api.sandbox.ovok.com/document' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \ -d '_count=20' \ -d '_offset=0' ``` - [Create document upload credentials](/api/document/create-document-upload-credentials): Creates a document and returns a signed form that you use to upload its file straight to storage. Use it for every new file: Ovok never receives the file bytes itself. **Auth:** bearer token. The document is created with your token, so you need create access to Binary and DocumentReference. **Scope:** the caller's project (from the token). ## Behaviour - Creates an empty Binary and a DocumentReference with `status: current`, the given `fileName` as title and `contentType`, and you as `author`. - `contentType` must be one of the allowed types: `image/jpeg`, `image/jpg`, `image/png`, `image/gif`, `image/webp`, `application/pdf`, `application/json`, `application/xml`, `text/plain`, `text/csv`, `video/mp4`, `video/webm`, `audio/mpeg`, `audio/wav`. - With `isPublic: "true"`, the document also gets a public token (`publicToken`) that anyone can use with `GET /document/public/{token}`. - `uploadOptions` holds a URL and form fields. Send a `multipart/form-data` POST to `uploadOptions.url` with every entry of `uploadOptions.fields` as a form field, then the file as the field `file`. The form expires 10 minutes after it is issued and accepts files up to 5 GB. - The document exists as soon as this call returns, even if you never upload the file. - This route is rate limited: 20 requests a minute for admins, 10 a minute for other users. ## Example ```bash curl -X POST 'https://api.sandbox.ovok.com/document' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \ -H 'Content-Type: application/json' \ -d '{ "fileName": "cat.png", "contentType": "image/png", "isPublic": "false" }' ``` ```json { "id": "87ea5dfc-8b8e-384d-8489-79496e706390", "meta": { "lastUpdated": "2026-01-01T12:00:00.000Z" }, "author": { "reference": "Practitioner/...", "display": "Max Mustermann" }, "fileName": "cat.png", "contentType": "image/png", "uploadOptions": { "url": "https://...", "fields": { "key": "...", "Policy": "...", "X-Amz-Signature": "...", ... } } } ``` Then upload the file with the returned form: ```bash curl -X POST '`, valid for 7 days. The share starts only when that practitioner, signed in, posts both values to `POST /v1/invites/record-share/accept`.
- The record is already shared with that practitioner: nothing is sent, unless their account has no password yet, in which case the set-password email goes out again.
- Anything else (a User that is not a practitioner of this project, or a practitioner with full project access): nothing is written or sent.
- Each email is sent as the last step of the write. Map the email templates named above in the project's email configuration first.
- Do not enable practitioner sharing in projects whose practitioners use the carehub dashboard: its route guard checks resource types only, so a practitioner who only holds shared patients could see project-wide data there.
- A 409 with `invitation_not_configured` means the project has no default practitioner access policy. It needs project configuration, not a retry.
- A 429 means the patient already has 20 pending offers.
### Related person
- **Body:** `email`, `firstName`, `lastName`, `codes` (1 to 100 Observation codes, each letters, digits, `.` and `-` only), and optional `includeMedications` (defaults to `false`).
- **Gates**, checked before the address is looked up, in this order:
- patient sharing (`enabled` on `PUT /v1/patient-sharing/settings`) is on; it is off by default;
- `RELATED_PERSON_INVITATION_ENABLED` is not `false` (403 `Invitation is not enabled for this project.`, as for a practitioner);
- the project has a patient app URL;
- the patient has fewer than 20 pending related-person invites.
- **What happens, by address:**
- No User has the address: a pending share is created and the address gets a `RELATED_PERSON_INVITED` email with an accept link, `/accept-invite?invite=&code=`, valid for 7 days. Nothing else is created yet: the account is made when the link is accepted (`POST /v1/invites/record-share/accept` with a `password`).
- The address belongs to exactly one active related person of the project: a pending share is created and the address gets a `RELATED_PERSON_RECORD_SHARED` email with the same kind of link. The share starts when that related person, signed in, accepts it.
- Anything else (a patient, a practitioner, or a User that is not a related person of this project): nothing is written or sent.
- The pending share is a `draft` Consent holding only a hash of the one-time code. It grants nothing until it is accepted.
- `RELATED_PERSON_INVITED` and `RELATED_PERSON_RECORD_SHARED` carry `firstName`, `lastName`, `sharerName` (the patient's name), `invitationUrl` (the accept link) and `baseUrl`. A project whose email configuration maps neither sends `PATIENT_INVITE` and `PATIENT_ASSIGNMENT_REQUEST` instead, with `inviterName` set to the patient's name.
- A 429 means the patient already has 20 pending related-person invites.
### Both
- The answer is always `202 { "status": "sent" }`, whichever case applied, so it does not reveal whether the address has an account. If the email cannot be sent, everything the invite wrote is undone, and the answer is still 202.
- A 409 with `app_url_not_configured` means the project has no app URL for the recipient: the practitioner app, or the patient app for a related person.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/invites/record-share' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{
"recipient": "practitioner",
"email": "max.mustermann@example.com",
"firstName": "Max",
"lastName": "Mustermann"
}'
```
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/invites/record-share' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{
"recipient": "related-person",
"email": "max.mustermann@example.com",
"firstName": "Max",
"lastName": "Mustermann",
"codes": ["8480-6", "8462-4"],
"includeMedications": true
}'
```
- [Accept a patient's record share](/api/invites/accept-a-patient-s-record-share): Accepts a patient's record share from its email: a practitioner's `PRACTITIONER_PATIENT_SHARED` offer, or a related person's `RELATED_PERSON_INVITED` or `RELATED_PERSON_RECORD_SHARED` invite. From then on the share is in place. The invite decides how it is accepted.
**Auth:** bearer token of the Practitioner or RelatedPerson the invite names; none for an invite to a new address.
**Scope:** the caller's project (from the token); for a new address, the invite's project.
## Behaviour
- Send the `invite` and `code` values from the email's accept link as `inviteId` and `code`. `password` is only for a new related person; send `null` (or leave it out) otherwise.
- Every invite is single use and expires 7 days after it was sent. A used one answers 409, an expired one 410, and an unknown one or a wrong `code` 404, whoever calls.
- On success the invite's Consent becomes `active` and its grant is added to the recipient's membership, in one atomic write. The recipient's cached sessions are refreshed, so the new access applies on the next request.
- The share lasts until the patient ends it (`DELETE /v1/patient-sharing/shares/{consentId}`).
### A practitioner's offer
- Signed in as the practitioner the offer names; without a token the answer is 401.
- Gates: practitioner sharing must be on for the project, and `PRACTITIONER_INVITATION_ENABLED` must not be `false`.
- The share covers the patient's whole compartment. Observations, Communications, DocumentReferences, QuestionnaireResponses, ServiceRequests, Tasks and Appointments can be read, searched, created and updated; CommunicationRequests, Goals, CarePlans, Flags, EpisodeOfCare and DeviceUseStatements are read-only; the Patient can be read, searched and updated. Nothing can be deleted.
- A 409 with `practitioner_without_access_policy` means the caller has full project access, which a share would narrow.
- The answer is the practitioner share: `consentId`, `sharer`, `practitioner`, `startedAt`, `endsAt`.
### A related person's invite
- Gate: patient sharing must be on for the invite's project.
- The share covers the sharer's Observations with the invite's codes (read and search), and the sharer's Patient resource with contact details, identifiers and most other fields hidden.
- A share also covers a multi-value reading's group Observation when it covers every value the group repeats (systolic and diastolic for blood pressure, the waveform for an ECG, any part of an SpO2 or urine-strip reading), so the whole reading can be read and nothing more. The share's `codes` stay as the sharer chose them.
- **A new address** (`RELATED_PERSON_INVITED`): no token. Send a `password` that meets the password policy; without one the answer is a 422 with `password_required`. In one atomic write under the address's account lock, the account is made: a User scoped to the project, a RelatedPerson of the sharer, and a membership holding the built-in related-person base policy (read and search of its own RelatedPerson only, created the first time it is needed) and the share's grant. If the address has any account by now, nothing is written and the answer is a 409 with `address_has_account`. Sign in afterwards with the address and that password.
- **A related person of the project** (`RELATED_PERSON_RECORD_SHARED`): signed in as the related person the invite names; without a token the answer is 401, as another related person or in another project 404. The grant is added to their membership, keeping its base policy.
- The answer is the related-person share: `consentId`, `sharer`, `relatedPerson`, `codes`, `startedAt`, `endsAt`.
- A 409 with `sharing_not_configured` means the project lost its patient-sharing template; turn patient sharing on again.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/invites/record-share/accept' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{
"inviteId": "c3499c27-2973-0a7f-807e-fb8676a92dcb",
"code": "",
"password": null
}'
```
A new related person, without a token:
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/invites/record-share/accept' \
-H 'Content-Type: application/json' \
-d '{
"inviteId": "c3499c27-2973-0a7f-807e-fb8676a92dcb",
"code": "",
"password": "Ex4mple-Passw0rd"
}'
```
- [Invite a patient into my care](/api/invites/invite-a-patient-into-my-care): A signed-in practitioner invites a patient into their care, by email. A new address gets a patient account whose record is already shared with the inviter; a patient of the project gets an invite to accept.
**Auth:** bearer token of a Practitioner.
**Scope:** the caller's project (from the token).
## Behaviour
- **Gates**, checked before the address is looked up, in this order:
- practitioner sharing (`practitionerSharingEnabled` on `PUT /v1/patient-sharing/settings`) is on; it is off by default;
- `PATIENT_INVITATION_ENABLED` is not `false`;
- the project has a patient app URL;
- the project has a default patient access policy;
- the inviter's membership has an access policy (a membership with full project access is refused, because a share would narrow it);
- the inviter has fewer than 20 pending invites.
- **What happens, by address:**
- No User has the address: a patient account of the project is created with the default patient access policy, and its record is shared with the inviter, in one atomic write. The address gets a `PATIENT_INVITE` email with a set-password link on the patient app.
- The address belongs to exactly one active patient of the project: a pending invite is created and the address gets a `PATIENT_ASSIGNMENT_REQUEST` email with an accept link, `/accept-invite?invite=&code=`, valid for 7 days. The share starts only when that patient, signed in, posts both values to `POST /v1/invites/care/accept`.
- The patient already shares their record with the inviter: nothing is sent, unless their account has no password yet, in which case the set-password email goes out again.
- Anything else (a User that is not a patient of this project): nothing is written or sent.
- The answer is always `202 { "status": "sent" }`, whichever case applied, so it does not reveal whether the address has an account. If the email cannot be sent, everything the invite wrote is undone, and the answer is still 202.
- Each email is sent as the last step of the write. Map the email templates named above in the project's email configuration first.
- Do not enable practitioner sharing in projects whose practitioners use the carehub dashboard: its route guard checks resource types only, so a practitioner who only holds shared patients could see project-wide data there.
- A 409 with `app_url_not_configured` means the project has no patient app URL; `invitation_not_configured` means it has no default patient access policy; `practitioner_without_access_policy` means the inviter has full project access. None of these is fixed by retrying.
- A 429 means the practitioner already has 20 pending invites.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/invites/care' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{
"email": "max.mustermann@example.com",
"firstName": "Max",
"lastName": "Mustermann"
}'
```
- [Accept a practitioner's care invite](/api/invites/accept-a-practitioner-s-care-invite): The signed-in patient accepts a practitioner's invite into their care from a `PATIENT_ASSIGNMENT_REQUEST` email. From then on their whole record is shared with the inviting practitioner.
**Auth:** bearer token of a Patient.
**Scope:** the caller's project (from the token).
## Behaviour
- Send the `invite` and `code` values from the email's accept link as `inviteId` and `code`.
- Gates: practitioner sharing must be on for the project, and `PATIENT_INVITATION_ENABLED` must not be `false`.
- Only the patient the invite names can accept it. The invite is single use and expires 7 days after it was sent.
- On success the invite's Consent becomes `active` and a grant for the caller's record is added to the practitioner's membership, in one atomic write. The practitioner's cached sessions are refreshed.
- The practitioner gets the access described on `POST /v1/invites/record-share/accept`: the patient's whole compartment, with no delete.
- The share lasts until the patient ends it (`DELETE /v1/patient-sharing/shares/{consentId}`).
- A 409 with `practitioner_without_access_policy` means the practitioner has full project access, which a share would narrow.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/invites/care/accept' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{
"inviteId": "c3499c27-2973-0a7f-807e-fb8676a92dcb",
"code": ""
}'
```
- IotDevice
- [Get Pulsene MQTT settings](/api/iotdevice/get-pulsene-mqtt-settings): Returns the MQTT connection settings for a Pulsene MC01 device. The device calls this route on startup to learn which broker to connect to.
**Auth:** None at the HTTP level (no bearer token). The body must carry an `apiKey` that matches the key the device firmware ships with. A wrong or missing key is rejected with 401.
## Behaviour
- The body must contain `serialNumber` (non-empty string) and `apiKey` (non-empty string).
- Ovok does not look the device up. Any serial that passes the key check receives the same response shape: `mqtt.clientId` is the serial you sent, `username` and `password` are the fixed MC01 broker credentials, and `url` is the broker URL configured for the environment.
- `wifi` and `pairing` are not returned for this device family.
- The route answers `200`. It does not write anything.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/iot-device/pulsene/setup' \
-H 'Content-Type: application/json' \
-d '{ "serialNumber": "", "apiKey": "" }'
```
- Localization
- [Get project locales](/api/localization/get-project-locales): Returns the languages enabled for your project and its default language.
**Auth:** bearer token. You need `List:read` and `List:search` access, or be a project admin.
**Scope:** the caller's project (from the token).
## Behaviour
- The locale settings are stored as a FHIR `List` in your project.
- If the project has no locale settings yet, the route returns 404. Create them with `PATCH /locales`.
- Localization and content routes accept only the languages returned here.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/locales' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Set project locales](/api/localization/set-project-locales): Sets the languages enabled for your project and its default language. Creates the locale settings if the project has none.
**Auth:** bearer token. You need `List:create`, `List:read`, `List:update`, `List:delete` and `List:search` access, or be a project admin.
**Scope:** the caller's project (from the token).
## Behaviour
- The body replaces the whole setting. `languages` needs at least one language, and `defaultLanguage` must be one of them.
- For each language that is new compared with the previous setting, background jobs create AI translations of all localization keys and content items from the previous default language. Existing translations are not changed.
- Removing a language does not delete its localizations or content.
- Changing `defaultLanguage` does not move or translate anything by itself.
## Example
```bash
curl -X PATCH 'https://api.sandbox.ovok.com/locales' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{ "languages": ["en-US", "de-DE", "fr-FR"], "defaultLanguage": "en-US" }'
```
- Localization Keys
- [Get i18next resources for a language](/api/localization-keys/get-i18next-resources-for-a-language): Returns all localizations of one language as a nested i18next resource object. Apps load it to initialise i18next.
**Auth:** bearer token.
**Scope:** the caller's project (from the token). Localizations are read and written with your token.
## Behaviour
- `language` must be one of the languages in your project's locale settings (`GET /locales`).
- Dot-separated keys are turned into nested objects: `home.screen.title` becomes `{ "home": { "screen": { "title": "..." } } }`.
- If a key is both a value and the parent of other keys, for example `home` and `home.title`, the nested object wins and the value of `home` is left out.
- At most 10,000 entries are included.
- The result per project and language is cached. A `PATCH` to this language becomes visible only after its background job has finished.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/localization/i18next/en-US' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Write i18next resources for a language](/api/localization-keys/write-i18next-resources-for-a-language): Writes many localizations of one language at once from a nested i18next resource object. The write runs in the background.
**Auth:** bearer token.
**Scope:** the caller's project (from the token). Localizations are read and written with your token.
## Behaviour
- `language` must be one of the languages in your project's locale settings (`GET /locales`).
- The body is a nested object. Keys may contain only letters, digits, `_` and `-`; nesting builds dot-separated keys. Each value is either a string of 1 to 4,096 characters or another nested object.
- Keys in the body are created or overwritten. Keys that are not in the body are not changed. Nothing is deleted.
- The route answers `202 Accepted` with no body as soon as the job is queued. The values are written by a background job, and the cache for the language is cleared when it finishes.
- A second background job per project language creates AI translations of the default-language values for keys that have no translation in that language yet. Existing translations are not changed.
## Example
```bash
curl -X PATCH 'https://api.sandbox.ovok.com/localization/i18next/en-US' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{ "home": { "title": "Home", "subtitle": "Welcome back" } }'
```
- [List localizations with translations](/api/localization-keys/list-localizations-with-translations): Lists localization keys as pairs of the default-language value and its translation in a target language. Use it for translation screens that show the original next to the translation.
**Auth:** bearer token.
**Scope:** the caller's project (from the token). Localizations are read and written with your token.
## Behaviour
- `target` must be one of the languages in your project's locale settings, and must not be the default language.
- Each result has `source` (the entry in the default language) and `target` (the entry in the target language) for the same key. `target` is missing when the key has no translation yet.
- Pairs are sorted by the target's `date`, newest first, falling back to the source's `date`. `_sort` is accepted but has no effect.
- `search` is a fuzzy match over both values, both keys and the target's author name.
- `_count` defaults to `10` and must be greater than 0. `_offset` defaults to `0`. `total` is the number of matching pairs before paging.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/localization/map/de-DE?search=home' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [List localizations for a language](/api/localization-keys/list-localizations-for-a-language): Lists the localization keys and values of one language, with optional fuzzy search and paging.
**Auth:** bearer token.
**Scope:** the caller's project (from the token). Localizations are read and written with your token.
## Behaviour
- `language` must be one of the languages in your project's locale settings (`GET /locales`).
- Results are sorted by `date`, newest first. `_sort` is accepted but has no effect.
- `search` is a fuzzy match over the value, the key and the author name.
- `_count` defaults to `10` and must be greater than 0. `_offset` defaults to `0`. `total` is the number of matching entries before paging.
- At most 10,000 entries per language are considered.
- The list per project and language is cached. Writes through these routes clear the cache for the language they change.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/localization/en-US?search=home&_count=20' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Get a localization](/api/localization-keys/get-a-localization): Returns the localization for one key in one language.
**Auth:** bearer token.
**Scope:** the caller's project (from the token). Localizations are read and written with your token.
## Behaviour
- `language` must be one of the languages in your project's locale settings (`GET /locales`).
- `key` is a dot-separated path such as `home.screen.title`. It may contain letters, digits, `_`, `-` and `.`, and must not start with `.`.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/localization/en-US/home.screen.title' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Delete a localization](/api/localization-keys/delete-a-localization): Deletes one key in one language, or in every language when you name the default language.
**Auth:** bearer token.
**Scope:** the caller's project (from the token). Localizations are read and written with your token.
## Behaviour
- `language` must be one of the languages in your project's locale settings (`GET /locales`).
- If `language` is the project's default language, the key is deleted in every project language. Otherwise only that language's entry is deleted.
- Deleting a key that does not exist succeeds and changes nothing.
- The response has no body.
## Example
```bash
curl -X DELETE 'https://api.sandbox.ovok.com/localization/en-US/home.screen.title' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Create or update a localization](/api/localization-keys/create-or-update-a-localization): Sets the value of one key in one language. Creates the localization if it does not exist yet.
**Auth:** bearer token.
**Scope:** the caller's project (from the token). Localizations are read and written with your token.
## Behaviour
- `language` must be one of the languages in your project's locale settings (`GET /locales`).
- `key` is a dot-separated path such as `home.screen.title`. It may contain letters, digits, `_`, `-` and `.`, and must not start with `.`.
- Ovok sets `author` to your profile. If more than one entry exists for this key and language, the first is kept and the others are deleted.
- After the write, a background job runs for every project language. It creates an AI translation of the default-language value for each language that has no entry for this key yet. Existing translations are not changed.
## Example
```bash
curl -X PUT 'https://api.sandbox.ovok.com/localization/en-US/home.screen.title' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{ "value": "Home" }'
```
- MCP
- [MCP endpoint (POST only)](/api/mcp/mcp-endpoint-post-only): This MCP server is stateless: use POST.
- [MCP endpoint](/api/mcp/mcp-endpoint): Ovok's MCP server, for Claude or any MCP client: it sets up the project it is connected to, as one of the project's admins.
**Auth:** an access token from Ovok's MCP OAuth server, never a Medplum token. Clients find it the standard way:
1. A `GET`, `POST` or `DELETE` without a token answers 401 with `WWW-Authenticate: Bearer resource_metadata="…/.well-known/oauth-protected-resource/mcp"`.
2. That document names the authorization server, `/mcp`. Its metadata is at `/.well-known/oauth-authorization-server/mcp`.
3. The client registers itself at `/mcp/register`, sends the user to `/mcp/authorize` (PKCE S256), and exchanges the code at `/mcp/token`. The answer to the client carries `iss`, this authorization server's issuer (RFC 9207), on allow and on deny.
Sign-in and consent happen on the Ovok console. Only an admin of the project can connect a client, and never to the Super Admin project.
## Behaviour
- Streamable HTTP, stateless: send every JSON-RPC message with `POST`, one per request. A batch answers 400. There are no sessions: `GET` and `DELETE` answer 405 to a caller with a valid token, and 401 without one.
- `server/discover` (MCP 2026-07-28) answers the protocol versions this server speaks, its `tools` capability and its name. None of those versions is 2026-07-28 yet: start with `initialize`.
- `/mcp`, its OAuth routes and both MCP metadata documents answer with `Strict-Transport-Security: max-age=31536000; includeSubDomains`.
- Every request checks the token, then that its account is still an admin of the project. An account that is no longer an admin gets 403 at once, whatever the token's expiry. When that can't be read, the answer is 503: try again.
- Tools act on the project the connection was granted for:
- `get_project`;
- `get_settings`, `set_setting`, `set_setting_value`;
- `list_members`, `invite_member`;
- `list_apps` (without the apps' secrets), `create_app`;
- `get_signals`.
- Each tool runs the same service as the matching API route, with the same rules. A refused call answers a tool error carrying the route's status and error body.
- Limits:
- Each admin may send 300 messages a minute. Over that, the answer is 429 with `Retry-After`.
- Each tool allows 60 calls a minute per admin; `create_app` and `invite_member` allow 10. Over that, the tool error has status 429 and `retryAfterSec`.
- Tool errors carry the route's status and body for a refusal (4xx). A failure (5xx) is logged and answered without its detail.
- MCP calls are not API usage: like the console's own calls, they are left out of statistics and the plan's monthly request cap.
- Access tokens last an hour. Refresh tokens last 30 days, and each one works once: a refresh answers a new pair. A repeat within 30 seconds, such as parallel refreshes, gets a pair of its own. A refresh token used again later ends the connection, and so does revoking either token. A connection ends 90 days after its consent, however often it refreshes: connect again.
## Example
Add it to Claude Code, which signs in through the browser on first use:
```bash
claude mcp add --transport http ovok https://api.sandbox.ovok.com/mcp
```
- [MCP endpoint (POST only)](/api/mcp/mcp-endpoint-post-only-2): This MCP server is stateless: use POST.
- Me
- Me/Consent
- [Get legal consent](/api/me/consent/get-legal-consent): Returns the signed-in practitioner's legal consent: which terms they accepted, when, and whether the acceptance is still active. Use it on app load to decide whether to ask for the terms again.
**Auth:** Bearer token for a practitioner session (the profile must be a `Practitioner`). The caller's access policy must grant `Consent:read` and `Consent:search` (a project admin who holds no access policy skips this check).
**Scope:** The caller's practitioner profile in the caller's project (from the token).
## Behaviour
- `legal` is `null` when the practitioner has never accepted the terms in this project.
- `status` is `active` or `revoked`. Publishing new terms revokes every acceptance on an older version with `revokedReason: "tos-updated"`; withdrawing revokes it with `revokedReason: "withdrawn"`. Both reasons are `null` while the consent is active.
- `revokedAt` is when the revoke happened, or for `tos-updated` when the new terms were published.
- `termsUrl` is the terms page the practitioner accepted. It is `null` unless it is an `http(s)` URL or an app-relative path.
- `termsUpdatedAt` is when the accepted terms were last updated, `null` when that is not known.
- The published terms version is the one of the root project of your project tree: a child project follows its parent's terms.
- Answers are cached for a rolling day. Any consent write by the practitioner and any publish of new terms refreshes them right away.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/me/consent/legal' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
```json
{
"legal": {
"consentId": "c3499c27-2973-0a7f-807e-fb8676a92dcb",
"status": "revoked",
"termsUrl": "https://app.example.com/legal/terms-and-conditions",
"termsUpdatedAt": "2026-09-01T08:00:00.000Z",
"acceptedAt": "2026-09-02T07:30:00.000Z",
"revokedAt": "2026-10-01T09:00:00.000Z",
"revokedReason": "tos-updated"
}
}
```
- [Accept or withdraw the terms](/api/me/consent/accept-or-withdraw-the-terms): Accepts or withdraws the terms for the signed-in practitioner and returns the resulting legal consent.
**Auth:** Bearer token for a practitioner session (the profile must be a `Practitioner`). The caller's access policy must grant `Consent:read`, `Consent:search`, `Consent:create` and `Consent:update` (a project admin who holds no access policy skips this check).
**Scope:** The caller's practitioner profile in the caller's project (from the token).
## Behaviour
- `termsAndConditionsConsent: true` records acceptance of the currently published terms. It creates the legal consent, or re-activates a revoked one, with a link to the terms page of your project's practitioner app and the time of acceptance.
- `termsAndConditionsConsent: false` revokes an active legal consent with `revokedReason: "withdrawn"`. With no active consent it changes nothing.
- Accepting again on the same terms version writes nothing.
- The terms value read by `GET /v1/slim/user/consents` changes in the same commit. Other consents are not changed.
- The response has the shape of `GET /v1/me/consent/legal`.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/me/consent/legal' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{ "termsAndConditionsConsent": true }'
```
- [Get medical consent](/api/me/consent/get-medical-consent): Returns the signed-in practitioner's medical settings notifications consent, or `null` when it is not given. Use it on app load to decide whether to show the medical notifications prompt.
**Auth:** Bearer token for a practitioner session (the profile must be a `Practitioner`). The caller's access policy must grant `Consent:read` and `Consent:search` (a project admin who holds no access policy skips this check).
**Scope:** The caller's practitioner profile in the caller's project (from the token).
## Behaviour
- `medical` is `null` when the practitioner never gave the consent or withdrew it.
- Only withdrawing ends it. Unlike `GET /v1/slim/user/consents`, a newly published version does not turn it off here.
- `acceptedAt` is when it was given, `null` when that is not recorded.
- Answers are cached for a rolling day. Any consent write by the practitioner refreshes them right away.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/me/consent/medical' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
```json
{ "medical": { "consentId": "c3499c27-2973-0a7f-807e-fb8676a92dcb", "acceptedAt": "2026-09-02T07:30:00.000Z" } }
```
- [Give or withdraw the medical consent](/api/me/consent/give-or-withdraw-the-medical-consent): Gives or withdraws the medical settings notifications consent for the signed-in practitioner and returns the resulting medical consent. Other consents are not changed.
**Auth:** Bearer token for a practitioner session (the profile must be a `Practitioner`). The caller's access policy must grant `Consent:read`, `Consent:search`, `Consent:create` and `Consent:update` (a project admin who holds no access policy skips this check).
**Scope:** The caller's practitioner profile in the caller's project (from the token).
## Behaviour
- `medicalSettingsNotificationsConsent: true` records the consent on the currently published version. `false` withdraws it.
- Sending the current value again writes nothing.
- The value read by `GET /v1/slim/user/consents` changes in the same commit.
- The response has the shape of `GET /v1/me/consent/medical`: `medical` is `null` after a withdrawal.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/me/consent/medical' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{ "medicalSettingsNotificationsConsent": true }'
```
- Organization Code Information
- [Email organization codes](/api/organization-code-information/email-organization-codes): Emails a user the tenant codes of every organization (project) they have an account in. Use it on a sign-in screen for users who forgot their organization code.
**Auth:** none.
## Behaviour
- The route always answers 202 with no body and does the work in the background, so the answer does not tell you whether the email has an account.
- The background job finds every account with this email, then looks up the tenant code of each project. If there are no accounts or no codes, no email is sent.
- The email lists each project's name and code. It uses the `PATIENT_ORGANIZATION_CODES` template when the first account found is a Patient, and `OVOK_ORGANIZATION_CODES` otherwise. Language and domain come from the request.
- The email address is lowercased.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/organizations/code/email' \
-H 'Content-Type: application/json' \
-d '{
"email": "max.mustermann@example.com"
}'
```
- [Get organization code](/api/organization-code-information/get-organization-code): Returns the tenant code of the caller's project. Users type the code on sign-in screens to choose their organization.
**Auth:** bearer token.
**Scope:** the caller's project (from the token).
## Behaviour
- The response holds `code` and `projectId`.
- If the project has no tenant code, the request fails with 400 (`Tenant code not found`).
- If more than one code row exists for a project, the most recently updated one is returned.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/organizations/code' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- Patient sharing
- [Get patient sharing settings](/api/patient-sharing/get-patient-sharing-settings): Returns the caller's project settings for data sharing: whether patients may share their data with each other, and whether the patient and practitioner invites (`/v1/invites`) are on.
**Auth:** bearer token of a project admin.
**Scope:** the caller's project (from the token).
## Behaviour
- `enabled`: patient-to-patient sharing is on. Off unless an admin turned it on.
- `accessPolicyId`: the ID of the read-only template AccessPolicy every patient-to-patient share points at, or `null` while none has been created.
- `practitionerSharingEnabled`: the `/v1/invites` routes are on. Off unless an admin turned it on.
- `practitionerAlertDigest`: whether the clinician alert digest is on and how many hours apart it goes out. Off, every 6 hours, unless an admin set it.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/patient-sharing/settings' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Update patient sharing settings](/api/patient-sharing/update-patient-sharing-settings): Turns patient-to-patient sharing, and the patient and practitioner invites (`/v1/invites`), on or off for the caller's project.
**Auth:** bearer token of a project admin.
**Scope:** the caller's project (from the token).
## Behaviour
- `enabled` is required. Turning it on creates the read-only sharing template AccessPolicy, or brings an existing one up to date, in the same atomic write as the setting. The template lets a recipient read and search the sharer's Observations with the shared codes, and read the sharer's Patient resource with contact details, identifiers and most other fields hidden.
- Turning `enabled` off stops new invites and redemptions. Existing shares stay until they are ended.
- `enabled` also lets a signed-in patient share Observation codes with a related person by email (`POST /v1/invites/record-share` with `"recipient": "related-person"`). A new address gets a related-person account when it accepts, with a built-in base policy that reads only its own RelatedPerson; it is created the first time it is needed. Map `RELATED_PERSON_INVITED` and `RELATED_PERSON_RECORD_SHARED`, or the `PATIENT_INVITE` and `PATIENT_ASSIGNMENT_REQUEST` templates they fall back to: an invite whose email cannot be sent is undone.
- `practitionerSharingEnabled` does the same for the patient and practitioner invites, with the practitioner share's template. Leave it out or send `null` to keep its current value.
- Both are off by default. The response is the settings after the write, as `GET /v1/patient-sharing/settings` returns them.
- With `practitionerSharingEnabled` on, a signed-in patient can create a practitioner account (with the project's default practitioner access policy) for a new address, and a practitioner can create a patient account, each holding the share. Make sure that policy gives a new practitioner nothing a patient should not hand out. Map the `PATIENT_INVITE`, `PATIENT_ASSIGNMENT_REQUEST`, `PRACTITIONER_INVITED_BY_PATIENT` and `PRACTITIONER_PATIENT_SHARED` email templates first: an invite whose email cannot be sent is undone.
- Do not turn on `practitionerSharingEnabled` in projects whose practitioners use the carehub dashboard: its route guard checks resource types only, so a practitioner who only holds shared patients could see project-wide data there.
- `practitionerAlertDigest` turns the clinician alert digest on or off: `{ "enabled": true, "everyHours": 6 }`, where `everyHours` is 1, 6, 12 or 24. Leave `everyHours` out to keep the saved hours (6 until set). Leave the whole field out, or send `null`, to keep the digest as it is.
- Switching it on needs Signals annotations (`SIGNALS_ANNOTATE_OBSERVATIONS`), `enabled` and `practitionerSharingEnabled` all on; otherwise the write is refused with 409 `SETTING_NOT_AVAILABLE`. A write that only keeps it on is not checked, so sharing can still be turned off. While any of the three is off, no digest goes out.
- Every `everyHours`, each practitioner on an active practitioner share gets one email listing their patients with new Signals alerts: the patient's name, how many new alerts, and a link to the patient's alerts in the dashboard. It carries no reading or value, and nothing goes out when there are no new alerts.
- It counts alerts from the moment it was switched on, never the backlog before. Switching it off, or one of its prerequisites going off, forgets where it was, so switching it on again starts afresh.
- Each digest covers alerts up to an hour before it goes out: alerts reach FHIR through a flush that can lag, and an alert that arrives more than an hour late is not counted.
- Map the `PRACTITIONER_ALERT_DIGEST` email template first.
- A 409 without a code means the project's settings changed during the write. Read the settings again and retry.
## Example
```bash
curl -X PUT 'https://api.sandbox.ovok.com/v1/patient-sharing/settings' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{ "enabled": true, "practitionerSharingEnabled": null, "practitionerAlertDigest": { "enabled": true, "everyHours": 6 } }'
```
- [Create a share invite](/api/patient-sharing/create-a-share-invite): The signed-in patient creates a one-time invite to share Observations with the given codes with another patient. Set `includeMedications` to `true` to also share patient-scoped MedicationStatements and MedicationAdministrations. Hand the `inviteId` and `code` to the other patient, who accepts with `POST /v1/patient-sharing/invites/redeem`.
**Auth:** bearer token of a Patient.
**Scope:** the caller's project (from the token).
## Behaviour
- Patient sharing must be on for the project (`PUT /v1/patient-sharing/settings`).
- `codes`: 1 to 100 Observation codes, each 1 to 64 letters, digits, `.` or `-`. Duplicates are removed.
- `includeMedications`: optional, defaults to `false`. When true, the recipient can read the sharer's MedicationStatements and MedicationAdministrations until the share ends.
- `endsAt` must be `null`: a share lasts until it is ended. A date is refused for now.
- The invite is stored as a draft `Consent`. Only a SHA-256 hash of `code` is stored, so `code` is returned once, in this response.
- The invite expires 15 minutes after it is created (`expiresAt`).
- A patient can hold at most 10 unexpired invites at once.
- A 429 means the patient already holds 10 unexpired invites.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/patient-sharing/invites' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{ "codes": ["8480-6", "8462-4"], "endsAt": null, "includeMedications": true }'
```
- [Accept a share invite](/api/patient-sharing/accept-a-share-invite): The signed-in patient accepts another patient's share invite, and from then on can read what it shares.
**Auth:** bearer token of a Patient.
**Scope:** the caller's project (from the token).
## Behaviour
- Patient sharing must be on for the project.
- The invite is single use, expires 15 minutes after it was created, and cannot be accepted by the patient who made it.
- On success the invite's `Consent` becomes `active` and names the caller as recipient, and a grant is added to the caller's membership, in one atomic write. The caller's cached sessions are refreshed, so the new access applies on the next request.
- The grant lets the caller read and search the sharer's Observations with the shared codes, and read the sharer's Patient resource with contact details, identifiers and most other fields hidden.
- A share also grants a multi-value reading's group Observation when it covers every value the group repeats (systolic and diastolic for blood pressure, the waveform for an ECG, any part of an SpO2 or urine-strip reading), so the whole reading can be read and nothing more. The share's `codes` stay as the sharer chose them.
- A 409 that says your account changed meanwhile can be retried as is.
- A 503 means sharing is on but its template AccessPolicy is missing.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/patient-sharing/invites/redeem' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{
"inviteId": "c3499c27-2973-0a7f-807e-fb8676a92dcb",
"code": ""
}'
```
- [List shares](/api/patient-sharing/list-shares): Lists the signed-in patient's active shares in both directions, the practitioners their whole record is shared with, and the related persons they share codes with. For a signed-in related person (a carer), lists the shares patients gave them.
**Auth:** bearer token of a Patient or a RelatedPerson.
**Scope:** the caller's project (from the token).
## Behaviour
- `outgoing`: the caller's active patient-to-patient shares (Consents where the caller is the sharer), including the `includeMedications` choice.
- `incoming`: what is shared with the caller, read from the grants on the caller's own membership, since those decide what the caller can see. Each share includes its `includeMedications` choice. A grant whose Consent cannot be read is still listed, so it can be ended.
- `practitioners`: the caller's active record shares with practitioners.
- `relatedPersons`: the caller's active shares with related persons (carers), each with its `codes` and `includeMedications` choice.
- `received`: for a related person, the active shares patients gave them: the sharing patient (`sharer`), the shared `codes`, `includeMedications` and `startedAt`. Only their own: another carer's shares never show. Empty for a patient.
- A related person gets only `received`; the other lists are empty.
- Names are the display names of the Patient, Practitioner and RelatedPerson profiles in the project.
- A practitioner gets 403.
- Each Consent search returns at most 100 entries; there is no paging.
- Pending invites (drafts) are not listed.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/patient-sharing/shares' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [End a share](/api/patient-sharing/end-a-share): Ends a share, cancels a pending invite, or declines a practitioner's invite. A related person (a carer) leaves a share they received. Access stops at once.
**Auth:** bearer token of a Patient or a RelatedPerson.
**Scope:** the caller's project (from the token).
## Behaviour
- Who may call it, by `consentId`:
- a patient-to-patient share: the sharer or the recipient;
- a pending patient-to-patient invite: the sharer (this cancels it);
- a share of the caller's record with a practitioner, or a practitioner's pending invite to the caller: the patient.
- a share of the caller's codes with a related person, or its pending invite: the patient;
- a share a patient gave the caller as their related person: the related person (see below).
- A practitioner gets 403.
- Every grant bound to the Consent is removed from the members who hold it, and the Consent is set to `inactive`, in one atomic write. The holders' cached sessions are refreshed.
- Calling it again on an ended share only sweeps any grants left behind, so retrying is safe.
- If the records keep changing during the write, it retries up to three times, then answers 409. Retry the call.
- Returns `204 No Content`.
## A related person leaving a share
- The Consent is set to `inactive`, as when the patient ends it, and its grant leaves the caller's own membership, in one atomic write. The caller's cached sessions are refreshed.
- The membership keeps its base policy. One that had lost it gets it back, so it never reads as full project access.
- `404` when the share is not one the caller received, for example another carer's or a patient-to-patient share. It never says whose it is.
- `409` when the share already ended, or a record changed during the write. Read the list again and retry.
## Example
```bash
curl -X DELETE 'https://api.sandbox.ovok.com/v1/patient-sharing/shares/c3499c27-2973-0a7f-807e-fb8676a92dcb' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- Payload CMS API Keys
- [List the project's CMS API keys](/api/payload-cms-api-keys/list-the-project-s-cms-api-keys): Lists the API keys of your current project's CMS content. A key unlocks public CMS delivery (`/v1/public/cms/...`) of this project's tenant only. The keys themselves are never returned.
**Auth:** bearer token of a project admin. The caller must also be a Practitioner, an admin or hold the System Owner access policy.
**Scope:** the caller's project (from the token).
## Behaviour
- `keys` is newest first. Each has `id`, `name`, `prefix` (the key's first 12 characters, to tell keys apart), `createdAt` and `isDefault`.
- Every project starts with one key, `isDefault: true` with `id` `default` and `createdAt: null`: the key public delivery took before keys were per project. It works until you revoke it. Its `prefix` is only its first 4 characters: the same key serves every project that has not revoked it.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/cms/api-keys' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Create a CMS API key](/api/payload-cms-api-keys/create-a-cms-api-key): Creates an API key for public CMS delivery (`/v1/public/cms/...`) of your current project's tenant. The answer carries the key itself, once: it is stored only as a hash and cannot be shown again.
**Auth:** bearer token of a project admin. The caller must also be a Practitioner, an admin or hold the System Owner access policy.
**Scope:** the caller's project (from the token).
## Behaviour
- `name` is 1 to 64 characters, trimmed.
- The key starts with `ovok_cms_`. Send it as `x-api-key`, `Authorization: Bearer` or `?apiKey=`, with the tenant it belongs to (`x-ovok-tenant-code`, `?tenant=`, `x-ovok-medplum-project-id` or `?projectId=`). Naming another project's tenant answers 401.
- A project holds at most 20 keys, the default key included: one more returns 409 `CMS_API_KEY_LIMIT`.
- A 409 without a code means the project changed while the server wrote. Retry.
- The answer is 201.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/cms/api-keys' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{ "name": "Marketing site" }'
```
- [Revoke a CMS API key](/api/payload-cms-api-keys/revoke-a-cms-api-key): Revokes one of your current project's CMS API keys. Public delivery stops taking it at once.
**Auth:** bearer token of a project admin. The caller must also be a Practitioner, an admin or hold the System Owner access policy.
**Scope:** the caller's project (from the token).
## Behaviour
- `id` is a key's `id` from `GET /v1/cms/api-keys`; `default` revokes the default key.
- A key the project does not hold returns 404 `CMS_API_KEY_NOT_FOUND`.
- A 409 means the project changed while the server wrote. Retry.
- Public responses a CDN already cached may still be served for up to a minute.
- The answer is 204.
## Example
```bash
curl -X DELETE 'https://api.sandbox.ovok.com/v1/cms/api-keys/default' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- Payload CMS Public Delivery
- [List published documents of a public collection](/api/payload-cms-public-delivery/list-published-documents-of-a-public-collection): Lists the published documents of a CMS collection for one tenant, in one language, one page at a time. Use it to show content such as release notes or articles in an app or website.
**Auth:** one of the following, checked in this order:
- The CMS public API key, in the `x-api-key` header, as `Authorization: Bearer `, or as the `apiKey` query parameter.
- No key, for collections configured as keyless (by default `legal-pages` and `translations`).
- A signed-in Ovok session (bearer token), which may only read its own tenant.
**Scope:** the tenant you name, by tenant code (`x-ovok-tenant-code` header or `tenant` query parameter) or by project id (`x-ovok-medplum-project-id` header or `projectId` query parameter). Headers win over query parameters, and a tenant code wins over a project id. If you name neither, a signed-in session reads its own tenant. Each Ovok deployment has one set of content; there is no environment to choose.
## Behaviour
- Any collection except the `users` and `tenants` system collections can be read.
- Only documents with status `published` are returned. The response is the CMS list envelope (`docs`, `totalDocs`, `page`, `limit`, and so on).
- `locale` is a language tag such as `de` or `en-US`. Without it, the CMS default locale is used. Missing translations fall back as configured in the CMS.
- `page` defaults to `1` and is limited to 1 to 100. `limit` defaults to `20` and is limited to 1 to 50. Values outside those ranges are clamped; values that are not whole numbers fall back to the default.
- `sort` is a top-level field name, optionally prefixed with `-` for descending order, for example `-publishedAt`. Without it, the CMS default order is used.
- Ovok also has a shared tenant whose published documents are added to every tenant's list. Where both have a document with the same `slug`, the tenant's own document wins. If either tenant has more than 200 published documents in the collection, only the tenant's own documents are returned.
- Responses are cached by Ovok for up to 60 seconds. A request that names a tenant gets `Cache-Control: public, s-maxage=60, stale-while-revalidate=300` and a `Vary` on the tenant and API key headers. A request that names no tenant gets `Cache-Control: no-store`. Error responses carry neither.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/public/cms/collections/release-notes/items?tenant=my-medical-project&locale=de&sort=-publishedAt&limit=10' \
-H 'x-api-key: '
```
- [Get a published document by slug](/api/payload-cms-public-delivery/get-a-published-document-by-slug): Returns one published document of a CMS collection by its slug. Use it for stable public pages and deep links, such as terms and conditions or a privacy policy.
**Auth:** one of the following, checked in this order:
- The CMS public API key, in the `x-api-key` header, as `Authorization: Bearer `, or as the `apiKey` query parameter.
- No key, for collections configured as keyless (by default `legal-pages` and `translations`).
- A signed-in Ovok session (bearer token), which may only read its own tenant.
**Scope:** the tenant you name, by tenant code (`x-ovok-tenant-code` header or `tenant` query parameter) or by project id (`x-ovok-medplum-project-id` header or `projectId` query parameter). Headers win over query parameters, and a tenant code wins over a project id. If you name neither, a signed-in session reads its own tenant. Each Ovok deployment has one set of content; there is no environment to choose.
## Behaviour
- Any collection except the `users` and `tenants` system collections can be read.
- Only a document with status `published` is returned. The response is the document itself, with the fields its collection defines.
- `slug` must start with a lowercase letter or digit and contain only lowercase letters, digits, `-`, `_` and `.`, up to 128 characters.
- `locale` is a language tag such as `de` or `en-US`. Without it, the CMS default locale is used. Missing translations fall back as configured in the CMS.
- If the tenant has no published document with this slug, Ovok returns the shared tenant's document with that slug, when one exists.
- Responses are cached by Ovok for up to 60 seconds. A request that names a tenant gets `Cache-Control: public, s-maxage=60, stale-while-revalidate=300` and a `Vary` on the tenant and API key headers. A request that names no tenant gets `Cache-Control: no-store`. Error responses carry neither.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/public/cms/collections/legal-pages/items/terms-and-conditions?tenant=my-medical-project&locale=en-US'
```
- [List published items of a content type](/api/payload-cms-public-delivery/list-published-items-of-a-content-type): Lists the published items of a user-defined content type for one tenant. First-class collections are read with `GET /v1/public/cms/collections/{collectionSlug}/items` instead.
**Auth:** one of the following:
- The CMS public API key, in the `x-api-key` header, as `Authorization: Bearer `, or as the `apiKey` query parameter.
- A signed-in Ovok session (bearer token), which may only read its own tenant.
**Scope:** the tenant you name, by tenant code (`x-ovok-tenant-code` header or `tenant` query parameter) or by project id (`x-ovok-medplum-project-id` header or `projectId` query parameter). Headers win over query parameters, and a tenant code wins over a project id. If you name neither, a signed-in session reads its own tenant. Each Ovok deployment has one set of content; there is no environment to choose.
## Behaviour
- Returns the CMS `content-items` whose content type has the slug `contentTypeSlug` and whose status is `published`, in the CMS list envelope.
- At most 100 items are returned. There is no paging, sorting or locale selection on this route.
- Responses are cached by Ovok for up to 60 seconds. A request that names a tenant gets `Cache-Control: public, s-maxage=60, stale-while-revalidate=300` and a `Vary` on the tenant and API key headers. A request that names no tenant gets `Cache-Control: no-store`. Error responses carry neither.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/public/cms/faq/items?tenant=my-medical-project' \
-H 'x-api-key: '
```
- Projects
- [Get current project settings](/api/projects/get-current-project-settings): Returns the settings of your current project: the on/off settings and the text settings such as app URLs and default access policies.
**Auth:** bearer token. The caller must be a Practitioner, a project admin, or hold the System Owner access policy.
**Scope:** the caller's project (from the token).
## Behaviour
- Settings are read from `Project.setting[]` by name.
- `settings` always has every on/off key. A key that is not stored, or not stored as a boolean, is `false`.
- `values` always has every text key. A key that is not stored, or is blank, is `null`.
- `DEFAULT_PATIENT_ACCESS_POLICY` is read from `Project.defaultPatientAccessPolicy`, as `AccessPolicy/`: the policy patient registration and patient invites give a new patient.
- Entries of `Project.setting[]` with other names are not returned.
## Saved and effective values
`settings` cannot tell a switch turned off from one never saved. Two records, with every on/off key, can:
- `overrides`: the saved value, `true` or `false`, or `null` when none is saved (or it is not stored as a boolean).
- `effectiveSettings`: what Ovok applies. The saved value, else the default below, and off while the feature lacks what it needs (the column on the right).
| Key | Default | Also needs |
| --- | --- | --- |
| `PATIENT_LOGIN_ENABLED`, `PRACTITIONER_LOGIN_ENABLED`, `RELATED_PERSON_LOGIN_ENABLED` | on | |
| `PATIENT_INVITATION_ENABLED` | on | `DEFAULT_PATIENT_ACCESS_POLICY`. |
| `PRACTITIONER_INVITATION_ENABLED`, `RELATED_PERSON_INVITATION_ENABLED` | on | |
| `PATIENT_REGISTRATION_ENABLED` | on | `DEFAULT_PATIENT_ACCESS_POLICY`, naming a policy that can be read. |
| `PRACTITIONER_REGISTRATION_ENABLED` | off | `DEFAULT_PRACTITIONER_ACCESS_POLICY` (or `CLINICIAN_INVITE_ACCESS_POLICY`): an AccessPolicy of this project. |
| `CLINICIAN_INVITE_ON_BUSINESS_EMAIL` | off | Effective `PATIENT_REGISTRATION_ENABLED`, and the default practitioner access policy as above. |
| `CONTENT_ENABLED`, `MAILING_ENABLED` | off | Markers the Console sets; Ovok does not act on them. |
| `SIGNALS_SEND_AUTO` | off | A Signals tenant of the project's own. Observations created through FHIR go to it, and a resident's first one enrols the resident in Signals; ingest readings go there on their own. |
| `SIGNALS_ANNOTATE_OBSERVATIONS` | as `SIGNALS_SEND_AUTO` | A Signals tenant of the project's own. Signals' alerts and episodes are written into the project as `DetectedIssue` and `Flag`, and an alert's reading gets its `interpretation`. Project roles need read on `DetectedIssue` and `Flag` to see them. |
| `SIGNALS_MTLS_ENABLED` | off | A Signals tenant of the project's own, and an environment whose edge forwards client certificates. Calls into Signals through `/v1/signals/api/*` must present a registered client certificate (`/v1/signals/certs`), and Signals presents its own client certificate on every webhook to the tenant. |
- App URLs are not folded in: they are in `values`, and a flow that needs a missing one reports it, for example an invite answers 409 `app_url_not_configured`.
- Some routes need more. The sharing invites (`/v1/invites`) also need practitioner sharing on, set through the patient-sharing settings.
- With `PRACTITIONER_LOGIN_ENABLED` off, a practitioner who is a project admin keeps refreshing an open session, so they can turn it back on.
- A default access policy that cannot be read at that moment counts as missing, as it does for the flows themselves.
- Every key is read from the first `Project.setting[]` entry of its name.
## App URLs
`PATIENT_APP_URL` and `PRACTITIONER_APP_URL` are the app roots that links in emails are built on: `https://app.example.com` gives `https://app.example.com/setpassword//`.
- Resolution order, per audience: the project's setting, then the parent project's, then the legacy setting (`PATIENT_SIGNUP_URL` or `CLINICIAN_DASHBOARD_URL`, on the project then its parent), then the request `Origin` for flows that allow it, then the deployment default.
- For set-password and invite links, only the origin of `PATIENT_SIGNUP_URL` is used: `https://app.example.com/register` gives `https://app.example.com`, and `vitalwise://register` gives `vitalwise://`. Set `PATIENT_APP_URL` if the patient app lives under a path.
- The continue-as-patient link (a business-address sign-up turned into a clinician invite) uses `PATIENT_SIGNUP_URL` in full whenever it is set, ahead of `PATIENT_APP_URL`.
- An app URL is an https URL on any host (http only on localhost), or an app deep link `scheme://[host][/path]`: `vitalwise://` gives `vitalwise:///setpassword//`. A web URL on this API's own host is refused, and a stored one is skipped when an email is sent.
- Only the canonical form is stored and used, without a trailing slash. Userinfo, a query, a fragment, an opaque `scheme:path`, and web, script or local schemes used as a deep link (`javascript:`, `data:`, `file:` and others) are refused on write and skipped on send.
- An invite with no app URL for its audience answers 409 `app_url_not_configured` and creates nothing.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/project/settings' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Update a project setting](/api/projects/update-a-project-setting): Turns one on/off setting of your current project on or off, for example `PRACTITIONER_INVITATION_ENABLED`.
**Auth:** bearer token of a project admin. The caller must also be a Practitioner, an admin or hold the System Owner access policy.
**Scope:** the caller's project (from the token).
## Behaviour
- `:key` must be one of the on/off keys returned in `settings` by `GET /v1/project/settings`. It may be URL-encoded.
- Writes the entry into `Project.setting[]`, adding it when missing. Every other entry is kept as it is.
- The write only succeeds if the project did not change since the server read it; otherwise it answers 409.
- The response is the full settings envelope after the write, as `GET /v1/project/settings` returns it.
- An unknown `:key` returns 400 `UNKNOWN_SETTING` with the accepted keys.
- `SIGNALS_SEND_AUTO` and `SIGNALS_ANNOTATE_OBSERVATIONS` also switch their wiring before the setting is saved: Medplum's `Subscription` for send-auto, Signals' `ovok-annotations` subscriber for annotations. Nothing is saved if that fails (502 `SETTING_WIRING_FAILED`). Turning either on for a project without its own Signals tenant answers 409 `SETTING_NOT_AVAILABLE`; a concurrent change of the same switch answers 409 too. While `SIGNALS_ANNOTATE_OBSERVATIONS` has no saved value it follows `SIGNALS_SEND_AUTO`, so switching send-auto also switches annotations' wiring.
- `SIGNALS_MTLS_ENABLED` switches the project's calls into Signals and Signals' webhooks to it to mutual TLS, in the order that keeps the stricter side first: on, Ovok starts refusing calls without a registered client certificate before Signals is switched; off, Signals is switched first. Turning it on with no registered certificate (`POST /v1/signals/certs`), without a Signals tenant of the project's own, or where the environment's edge forwards no client certificates answers 409 `SETTING_NOT_AVAILABLE`. A Signals that does not keep the switch answers 502 `SETTING_WIRING_FAILED`, and nothing is saved.
- Read the project again and retry on 409.
## Example
```bash
curl -X PUT 'https://api.sandbox.ovok.com/v1/project/settings/PRACTITIONER_INVITATION_ENABLED' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{ "enabled": true }'
```
- [Update a project text setting](/api/projects/update-a-project-text-setting): Sets one text setting of your current project, such as an app URL or a default access policy, or removes it with `null`.
**Auth:** bearer token of a project admin. The caller must also be a Practitioner, an admin or hold the System Owner access policy.
**Scope:** the caller's project (from the token).
## Behaviour
- `:key` must be one of the text keys returned in `values` by `GET /v1/project/settings`. It may be URL-encoded.
- `value` is trimmed and checked for the key's kind:
- App URL keys (`PATIENT_APP_URL`, `PRACTITIONER_APP_URL`, `PATIENT_SIGNUP_URL`, `CLINICIAN_DASHBOARD_URL`): see App URLs below. The canonical form is stored.
- Access policy keys (`DEFAULT_PATIENT_ACCESS_POLICY`, `DEFAULT_PRACTITIONER_ACCESS_POLICY`, `CLINICIAN_INVITE_ACCESS_POLICY`): `AccessPolicy/` of an AccessPolicy in this project.
- `null` removes the entry. Every other entry of `Project.setting[]` is kept as it is.
- `DEFAULT_PATIENT_ACCESS_POLICY` is written to `Project.defaultPatientAccessPolicy`, not to `Project.setting[]`. Removing it turns patient registration and patient invitations off: see `effectiveSettings` in `GET /v1/project/settings`.
- The write only succeeds if the project did not change since the server read it; otherwise it answers 409.
- The response is the full settings envelope after the write, as `GET /v1/project/settings` returns it.
## App URLs
`PATIENT_APP_URL` and `PRACTITIONER_APP_URL` are the app roots that links in emails are built on: `https://app.example.com` gives `https://app.example.com/setpassword//`.
- Resolution order, per audience: the project's setting, then the parent project's, then the legacy setting (`PATIENT_SIGNUP_URL` or `CLINICIAN_DASHBOARD_URL`, on the project then its parent), then the request `Origin` for flows that allow it, then the deployment default.
- For set-password and invite links, only the origin of `PATIENT_SIGNUP_URL` is used: `https://app.example.com/register` gives `https://app.example.com`, and `vitalwise://register` gives `vitalwise://`. Set `PATIENT_APP_URL` if the patient app lives under a path.
- The continue-as-patient link (a business-address sign-up turned into a clinician invite) uses `PATIENT_SIGNUP_URL` in full whenever it is set, ahead of `PATIENT_APP_URL`.
- An app URL is an https URL on any host (http only on localhost), or an app deep link `scheme://[host][/path]`: `vitalwise://` gives `vitalwise:///setpassword//`. A web URL on this API's own host is refused, and a stored one is skipped when an email is sent.
- Only the canonical form is stored and used, without a trailing slash. Userinfo, a query, a fragment, an opaque `scheme:path`, and web, script or local schemes used as a deep link (`javascript:`, `data:`, `file:` and others) are refused on write and skipped on send.
- An invite with no app URL for its audience answers 409 `app_url_not_configured` and creates nothing.
- An unknown `:key` returns 400 `UNKNOWN_SETTING` with the accepted keys. A web app URL on this API's own host returns 400 `APP_URL_NOT_ALLOWED`. A value that is not valid for the key returns 422 `INVALID_SETTING_VALUE`.
## Example
```bash
curl -X PUT 'https://api.sandbox.ovok.com/v1/project/settings/values/PRACTITIONER_APP_URL' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{ "value": "https://app.example.com" }'
```
- [List administered projects](/api/projects/list-administered-projects): Lists every project you administer, with the tenant code you need to switch to it. Every other `/v1/projects` route answers about the single project of your token; use this one to build a project picker.
**Auth:** bearer token. The caller must be a Practitioner, a project admin, or hold the System Owner access policy. You do not need to be an admin of your current project.
**Scope:** your own user's Practitioner memberships across all projects, kept only where the membership is an admin one.
## Behaviour
- One row per project, in membership order, even when you hold two memberships on one project.
- `page` (from 0, default 0) and `count` (1 to 100, default 20) page over the projects. `total` counts the projects you administer, not your memberships.
- `tenantCode` is `null` for a project that has none. `name` is `null` when the project can no longer be read; the row is kept.
- `isMainProject` is `true` for a project with no parent, `false` for a child, and `null` when the hierarchy could not be read.
- `parentProjectId` is set only when the parent is also a project you administer; otherwise it is `null`.
- To switch, pass `tenantCode` to `POST /v1/auth/tenant/Practitioner/login/token`.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/projects?page=0&count=20' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [List current project members](/api/projects/list-current-project-members): Lists the Practitioner members of your current project, one page at a time.
**Auth:** bearer token. The caller must be a Practitioner, a project admin, or hold the System Owner access policy.
**Scope:** the caller's project (from the token).
## Behaviour
- Only memberships with a Practitioner profile are returned; patient, bot and client memberships are left out.
- Filters: `accessPolicyId` (the membership's access policy), `profileId` (a Practitioner id) and `userId`.
- `page` (from 0, default 0) and `count` (1 to 100, default 20) page the results. `total` is the exact number of matches.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/projects/me/members?page=0&count=20' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Invite a Practitioner to the project](/api/projects/invite-a-practitioner-to-the-project): Adds a Practitioner to your current project and emails them a link to set their password.
**Auth:** bearer token of a project admin (`ProjectMembership.admin`).
**Scope:** the caller's project (from the token). The project setting `PRACTITIONER_INVITATION_ENABLED` must be on.
## Behaviour
- `email` is trimmed and lowercased. `firstName` and `lastName` default to `New` and `Member`; `admin` defaults to `false`.
- Creates the User (when the address has no account yet), the Practitioner and the membership in one write.
- The invitee is emailed a link to set a password, built on the project's practitioner app URL (the project's `PRACTITIONER_APP_URL`, then the parent project's, then the legacy `CLINICIAN_DASHBOARD_URL`, then your request's `Origin` when CORS allows it, then the deployment default). The response has `pending: true` and `existingAccount: false`.
- An address that can already sign in (it has a password, or belongs to another project) gets the membership but no email, and the response has `existingAccount: true`.
- If the email cannot be sent, the new account is removed again and the request fails.
- `access` sets the membership's `access[]` entries. Each must name an AccessPolicy of this project. With `access`, the membership's base `accessPolicy` is the project's `DEFAULT_PRACTITIONER_ACCESS_POLICY` setting.
- A member with this email already exists on the project: 409. Setting `access` on a project with no default practitioner access policy: 409 `invitation_not_configured`. No practitioner app URL resolves: 409 `app_url_not_configured`, and nothing is created.
- Practitioner invitations disabled on the project: 403.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/projects/me/members' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{
"email": "max.mustermann@example.com",
"firstName": "Max",
"lastName": "Mustermann",
"admin": false
}'
```
- [Get a project member](/api/projects/get-a-project-member): Reads one Practitioner member of your current project.
**Auth:** bearer token of a project admin (`ProjectMembership.admin`).
**Scope:** the caller's project (from the token). A membership of another project answers 404.
## Behaviour
- `emailVerified` is `true` once the member has used an emailed set-password link, and `null` when the User cannot be read.
- `pending` is always `false` on this route.
- The route requires a Practitioner membership; any other membership type is refused with 400.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/projects/me/members/c3499c27-2973-0a7f-807e-fb8676a92dcb' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Update a project member](/api/projects/update-a-project-member): Changes a Practitioner member of your current project: their admin flag, their `access[]` entries, or both.
**Auth:** bearer token of a project admin (`ProjectMembership.admin`).
**Scope:** the caller's project (from the token). A membership of another project answers 404.
## Behaviour
- `admin`: `true` or `false` sets the flag; `null` leaves it as it is.
- `access`: replaces the membership's `access[]` entries; an empty array removes them. Omit it to leave them as they are. Each entry must name an AccessPolicy of this project. A membership without a base `accessPolicy` cannot have its entries removed, because Medplum would then grant access to the whole project.
- `versionId`: pass the `meta.versionId` you read. If the membership changed since, the request answers 409. A concurrent write that lands first also answers 409.
- A request that changes nothing returns the member unchanged.
- Demoting the last admin of the project is refused.
- After the change, Ovok renews its cached session data for every session of the member. Medplum applies the change on the member's next request.
## Example
```bash
curl -X PATCH 'https://api.sandbox.ovok.com/v1/projects/me/members/c3499c27-2973-0a7f-807e-fb8676a92dcb' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{ "admin": true }'
```
- [Remove a project member](/api/projects/remove-a-project-member): Removes a Practitioner from your current project.
**Auth:** bearer token of a project admin (`ProjectMembership.admin`).
**Scope:** the caller's project (from the token). A membership of another project answers 404.
## Behaviour
- Deletes the membership and its Practitioner profile. The User is deleted only when it has no other membership in any project.
- All or nothing: if one delete fails, the others are undone and the request fails.
- Removing the last admin of the project is refused.
- The response is `{ "success": true }`.
## Example
```bash
curl -X DELETE 'https://api.sandbox.ovok.com/v1/projects/me/members/c3499c27-2973-0a7f-807e-fb8676a92dcb' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Get current project features](/api/projects/get-current-project-features): Returns the Medplum features (`Project.features`) enabled on your current project, such as `bots` or `cron`.
**Auth:** bearer token. The caller must be a Practitioner, a project admin, or hold the System Owner access policy.
**Scope:** the caller's project (from the token).
## Behaviour
- Values are returned as stored, without checking them against the list `PATCH` accepts. An empty array means no features are enabled.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/projects/me/features' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Update current project features](/api/projects/update-current-project-features): Replaces the Medplum features (`Project.features`) of your current project with the list you send.
**Auth:** bearer token of a project admin. The caller must also be a Practitioner, an admin or hold the System Owner access policy.
**Scope:** the caller's project (from the token).
## Behaviour
- The list replaces the stored one; it is not merged. Send every feature you want enabled. Sending the same list twice has the same result.
- Each value must be one of the FHIR `Project.features` codes.
- The write only succeeds if the project did not change since the server read it; otherwise it answers 409.
## Example
```bash
curl -X PATCH 'https://api.sandbox.ovok.com/v1/projects/me/features' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{ "features": ["bots", "cron", "websocket-subscriptions"] }'
```
- Public Resource Access
- [Get a public resource](/api/public-resource-access/get-a-public-resource): Reads a FHIR resource through its public token, without a bearer token. Use it to share one resource, such as a form or a document, with people who have no Ovok account.
**Auth:** none. The public token in the path is the credential: anyone with it can read the resource.
## Behaviour
- A resource becomes public when it gets a token in `meta.extension`, with the URL `https://api.ovok.com/fhir/StructureDefinition/public-resource-token`. Use `PATCH /public/{resourceType}/{id}` to give a resource a token.
- The token names exactly one resource. The response is that resource as stored, including its `meta.extension`.
- A token stops working when the resource's token is replaced or deleted, or when the resource is deleted.
## Example
```bash
curl 'https://api.sandbox.ovok.com/public/example.jwt.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Change public resource token](/api/public-resource-access/change-public-resource-token): Gives a resource a new public token, so it can be read with `GET /public/{token}`. If the resource already has a token, the old one is replaced and stops working.
**Auth:** bearer token of a project admin.
## Behaviour
- The resource must be in the caller's project or one of its sub-projects. A resource of any other project, the parent project included, answers `403` and is left unchanged; an unknown id answers `404`.
- The token is written to the resource's `meta.extension` with the URL `https://api.ovok.com/fhir/StructureDefinition/public-resource-token`. Other extensions are kept.
- The response is `{ token, resource }`: the new token and the updated resource.
## Example
```bash
curl -X PATCH 'https://api.sandbox.ovok.com/public/DocumentReference/87ea5dfc-8b8e-384d-8489-79496e706390' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
```json
{
"token": "...",
"resource": {
"resourceType": "DocumentReference",
"id": "87ea5dfc-8b8e-384d-8489-79496e706390",
"meta": {
"extension": [
{ "url": "https://api.ovok.com/fhir/StructureDefinition/public-resource-token", "valueString": "..." }
]
},
...
}
}
```
- [Delete public resource token](/api/public-resource-access/delete-public-resource-token): Removes the public token from a resource, so it can no longer be read with `GET /public/{token}`. The resource itself is kept.
**Auth:** bearer token of a project admin.
## Behaviour
- The resource must be in the caller's project or one of its sub-projects. A resource of any other project, the parent project included, answers `403` and is left unchanged; an unknown id answers `404`.
- Removes the `meta.extension` entry with the URL `https://api.ovok.com/fhir/StructureDefinition/public-resource-token`. Other extensions are kept.
- Calling it on a resource without a token succeeds.
- The response is `{ token: null, resource }` with the updated resource.
## Example
```bash
curl -X DELETE 'https://api.sandbox.ovok.com/public/DocumentReference/87ea5dfc-8b8e-384d-8489-79496e706390' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- Reset Password
- [Send reset password email](/api/reset-password/send-reset-password-email): Sends a password reset email to a Patient, a RelatedPerson (carer) or a Practitioner. The answer is the same whether or not an account exists, so the route cannot be used to find out which emails are registered.
**Auth:** none.
**Scope:** for a Patient or RelatedPerson, the project that owns `clientId`. For a Practitioner, the account is found by email across projects.
## Behaviour
- `type` is `Patient`, `RelatedPerson` or `Practitioner`. `clientId` is required when `type` is `Patient` or `RelatedPerson` and is ignored for a Practitioner.
- The response is always a FHIR `OperationOutcome` saying "If the user exists, a password reset email has been sent." It is returned for unknown emails, unknown client IDs and accounts that could not be mailed.
- When the account exists, Ovok creates a reset request, which replaces any earlier open reset request for the user, and emails the user. Patients and RelatedPersons get the `PATIENT_RESET_PASSWORD` template and Practitioners the `RESET_PASSWORD` template.
- The email carries a link `/setpassword//` plus the request `id` and `secret`. The app URL is the project's patient app URL for a Patient or RelatedPerson, and its practitioner app URL for a Practitioner; if none is set, the request's `Origin` is used when it is an allowed one.
- A Practitioner who is a member of more than one project gets a link to the Ovok platform app instead of a project's app URL.
- If no app URL can be found, the email still goes out, without a link. The `id` and `secret` in it can be sent to `POST /v2/auth/reset-password/process`.
- If a Practitioner has several accounts, the first one found is used.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v2/auth/reset-password' \
-H 'Content-Type: application/json' \
-d '{
"email": "max.mustermann@example.com",
"clientId": "c3499c27-2973-0a7f-807e-fb8676a92dcb",
"type": "Patient"
}'
```
- [Reset user password with the information from the email.](/api/reset-password/reset-user-password-with-the-information-from-the-email): Sets a new password using the `id` and `secret` from a reset email. Use it when your app handles the reset itself instead of sending the user to the emailed link.
**Auth:** none. The reset request `id` and `secret` are the credential.
## Behaviour
- The `id` and `secret` come from the email sent by `POST /v2/auth/reset-password`. The `password` is the new password.
- The password must have at least 12 characters, not counting spaces at either end, and must not be on Ovok's list of common and breached passwords. Any character is allowed and none is required. A refused password answers 422 with the error on `password`.
- An invite link (from `POST /auth/invite` or a practitioner invite) sets a password for 7 days after it was sent. After that it answers 410 `This invite link has expired. Ask for a new invite.`, and inviting the user again sends a fresh link. Reset links are not limited this way.
- A request can be used once. Ovok marks it used before it changes the password, so a request whose password change then fails cannot be retried and the user must ask for a new email.
- Setting the password also marks the user's email as verified, because the secret came by email.
- The response is a minimal `User` with `resourceType`, `id` and `email`.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v2/auth/reset-password/process' \
-H 'Content-Type: application/json' \
-d '{
"id": "c3499c27-2973-0a7f-807e-fb8676a92dcb",
"secret": "k3J9xQ2mV7",
"password": "Ex4mple-Passw0rd"
}'
```
- Signals
- [List the project's client certificates](/api/signals/list-the-project-s-client-certificates): Lists the client certificates registered for your current project. With `SIGNALS_MTLS_ENABLED` on, every call into Signals through `/v1/signals/api/*` must present one of them.
**Auth:** bearer token of a project admin. The caller must also be a Practitioner, an admin or hold the System Owner access policy. A client credential cannot read or change them.
**Scope:** the caller's project (from the token).
## Behaviour
- Each certificate is known by `sha256`, its SHA-256 fingerprint in lowercase hex. Ovok never holds a private key.
- A project holds at most two: the one in use and one to rotate to.
- A certificate past `notAfter` stays listed until it is removed, but calls presenting it are refused.
- `environment` says what this Ovok environment can do: `issues` (a CA to issue from a CSR), `registers` (a CA it trusts for registration) and `forwardsCertificates` (an edge that passes client certificates on, which `SIGNALS_MTLS_ENABLED` needs). Check it before offering an action that would answer 501 or 409.
- A project without a Signals tenant of its own answers 409 `SIGNALS_TENANT_NOT_OWN`.
## Example
```bash
curl 'https://api.sandbox.ovok.com/v1/signals/certs' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Register a client certificate](/api/signals/register-a-client-certificate): Registers a client certificate for your current project. Calls into Signals may then present it, and must once `SIGNALS_MTLS_ENABLED` is on.
**Auth:** bearer token of a project admin. The caller must also be a Practitioner, an admin or hold the System Owner access policy. A client credential cannot register a certificate for itself.
**Scope:** the caller's project (from the token).
## Behaviour
- Send the certificate as PEM in `certificate`. Only its first certificate is read. Never send the private key: it stays with your client.
- The certificate must be valid now, not a CA certificate, for client authentication (or have no extended key usage), and issued by a CA your Ovok environment trusts. Otherwise 422 `CERTIFICATE_NOT_ACCEPTED`, with the reason.
- An environment with no CA set up for client certificates answers 501 `FEATURE_NOT_CONFIGURED`.
- The same certificate twice answers 409 `CERTIFICATE_ALREADY_REGISTERED`; a third certificate 409 `CERTIFICATE_LIMIT_REACHED`.
- The answer is 201 with the registered certificate, and `Location` names it.
- To rotate: register the new certificate, move your clients to it, then remove the old one.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/signals/certs' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d "{ \"certificate\": $(jq -Rs . < client.crt) }"
```
- [Issue a client certificate from a CSR](/api/signals/issue-a-client-certificate-from-a-csr): Issues a client certificate for your current project from a certificate signing request (CSR), and registers it. Calls into Signals may then present it, and must once `SIGNALS_MTLS_ENABLED` is on.
**Auth:** bearer token of a project admin. The caller must also be a Practitioner, an admin or hold the System Owner access policy. A client credential cannot issue a certificate for itself.
**Scope:** the caller's project (from the token).
## Behaviour
- Make a key and a CSR on your side, and send the CSR as PEM in `csr`. The private key never leaves your client.
- The key must be an EC key on P-256 or P-384, or an RSA key of at least 2048 bits, and the CSR's signature must verify. Otherwise 422 `CSR_NOT_ACCEPTED`, with the reason.
- Ovok's CA signs a certificate for client authentication only, valid for one year. Its subject is `CN=` and its subject alternative name `urn:ovok:signals-tenant:`. Nothing else in the CSR is taken over.
- The certificate counts towards the project's two: a third answers 409 `CERTIFICATE_LIMIT_REACHED`.
- An environment with no CA set up to issue answers 501 `FEATURE_NOT_CONFIGURED`. If its CA has expired, Ovok registers nothing and answers 500 `INTERNAL_ERROR`.
- The answer is 201 with the registered certificate and its PEM in `certificate`, and `Location` names it.
- To rotate: issue a new certificate, move your clients to it, then remove the old one.
## Example
```bash
openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes \
-keyout client.key -out client.csr -subj "/CN=client"
curl -X POST 'https://api.sandbox.ovok.com/v1/signals/certs/issue' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d "{ \"csr\": $(jq -Rs . < client.csr) }" | jq -r .certificate > client.crt
```
- [Remove a client certificate](/api/signals/remove-a-client-certificate): Removes one of your current project's client certificates. From then on, calls presenting it are refused, even while it was the project's only one and `SIGNALS_MTLS_ENABLED` is on.
**Auth:** bearer token of a project admin. The caller must also be a Practitioner, an admin or hold the System Owner access policy.
**Scope:** the caller's project (from the token).
## Behaviour
- `:sha256` is the certificate's fingerprint as `GET /v1/signals/certs` lists it.
- An unknown fingerprint answers 404 `CERTIFICATE_NOT_FOUND`.
- The answer is 204 with no body.
## Example
```bash
curl -X DELETE 'https://api.sandbox.ovok.com/v1/signals/certs/3f6c0d2a9b8e7f1c4d5a6b7c8d9e0f1a2b3c4d5e6f708192a3b4c5d6e7f80912' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- Signals API - Alerts: Alerts raised by evaluation. An alert opens on a breach, deduplicates on its `TRIGGER:` key while it stays open, and closes when a trigger resolves it.
- [List alerts, or the episodes they were folded into](/api/signals-api---alerts/list-alerts-or-the-episodes-they-were-folded-into): The dashboard view.
By default this is one row per breach, in v1's alert shape plus every nest alert field, inside v1's paginate envelope (`items`, `totalItems`, `page`, `totalPages`, …) with nest's `nextCursor` beside it. Page with v1's `page` and `limit` (a page may start at most 10000 rows in), or walk with `cursor`: pass `nextCursor` back verbatim. `sort` is v1's `time-desc` (the default) or `time-asc`; `ids` narrows to those patients. It works whether or not the tenant has `episodicAlerts`. Without `project` it is tenant-wide: a per-facility view must send `project`, which keeps only patients stamped with it.
`?episodes=true` answers with episodes instead, as `{items, nextCursor}`: one row per clinical event, which opens on the first breaching reading, absorbs the rest, and closes on recovery, on going stale, or by hand. That grain REQUIRES `project`. It defaults to the tenant's `feedCodes` (`PATCH /api/tenant`), every code when it lists none; alerts have no code default.
`codes`, `since` and `acknowledged` filter either grain. `until`, `page` and `ids` are alerts only: sending one with `episodes=true` is a 422, not a filter that silently did nothing. `status` reads ACTIVE/RESOLVED for alerts and OPEN/CLOSED for episodes.
Episodes are newest first by `sort`: `openedAt`, the default, or `lastFiringAt`, the newest firing reading (the opening, for an episode that has none). `since` and the cursor use the same time. `openedAt` never changes, so walking every page lists each episode once. On `lastFiringAt` an episode that fires mid-walk moves above the cursor: it is never listed twice, but that walk misses it. A `sort` value from the other grain is a 422.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_alerts:read`. Project admins have every capability; client applications get no admin bypass.
- ovok sets `project` to your project, so you can leave it out.
- If your access covers only some patients (anyone but project admins, manager or submitter apps, and anyone whose Patient access has no criteria), name them with `patientId` or `ids`, each one you can read, at most 100. Signals then pages over just those patients, so pages and totals stay true. A patient is named by its Signals id, or by its Medplum Patient id where Signals takes a `refId`; ovok asks Signals which patient a Signals id is.
- [Get one alert, or one episode with its readings](/api/signals-api---alerts/get-one-alert-or-one-episode-with-its-readings): The path segment is an alert id, or an episode id with `episodes=true`. An alert reads whether or not the tenant has `episodicAlerts`.
An episode comes with its summary, its acknowledgement and every value recorded for its code while it ran. The series starts at the first breaching reading. A long episode returns a capped series and sets `truncated`.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_alerts:read`. Project admins have every capability; client applications get no admin bypass.
- ovok sets `project` to your project, so you can leave it out.
- If your access covers only some patients (anyone but project admins, manager or submitter apps, and anyone whose Patient access has no criteria), the alert's patient must be one you can read: ovok reads the alert first. A patient is named by its Signals id, or by its Medplum Patient id where Signals takes a `refId`; ovok asks Signals which patient a Signals id is.
- [Acknowledge an alert, or an episode](/api/signals-api---alerts/acknowledge-an-alert-or-an-episode): The path segment is an alert id, or an episode id with `episodes=true`. Acknowledging closes it: an alert resolves, an episode ends its alerting window, and the next breaching reading opens a new one. The patient's triggers decide what else closing does (their `onAck` hooks). Under the default pack, acknowledging an open out-of-bed alert (103214-3) also silences the absence under way.
Idempotent: acknowledging again returns the original time and actor. Acknowledging one that already closed only stamps the acknowledgement.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_alerts:update`. Project admins have every capability; client applications get no admin bypass.
- ovok sets `project` to your project, so you can leave it out.
- If your access covers only some patients (anyone but project admins, manager or submitter apps, and anyone whose Patient access has no criteria), the alert's patient must be one you can update: ovok reads the alert first. A patient is named by its Signals id, or by its Medplum Patient id where Signals takes a `refId`; ovok asks Signals which patient a Signals id is.
- Signals API - Clients: API credentials and their webhook subscriptions.
- [List users](/api/signals-api---clients/list-users): The tenant's API clients, newest first, in v1's paginate envelope (`page`, `limit` defaulting to 10) with nest's `nextCursor`. `role` and `name` filter as in v1.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_clients:read`. Project admins have every capability; client applications get no admin bypass.
- [Create a user](/api/signals-api---clients/create-a-user): Mints an API client. `clientId` and `secret` are the Basic credential, chosen by the caller and never returned. `role` may be v1's lowercase. A SUBSCRIBER needs `webhookUrl` and at least one event. It is on `eventSchema` 2 unless the body says 1: on 2 its first Standard Webhooks signing secret is returned this once as `signingSecret`; on 1 it needs `webhookSecret` (v1 called it `secret`). `reach` is `attached` (default: the patients attached to it, as v1's) or `all` (every patient of the tenant, those enrolled later included).
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_webhooks:update`, `signals_events:read`. Project admins have every capability; client applications get no admin bypass.
- Only subscribers are created through ovok, on event schema 2. ovok generates their id and Signals login, which they never use, and Signals generates the webhook signing secret.
- It can reach any patient of your project without naming one, so it is for callers that reach every patient: project admins, manager or submitter apps, and anyone whose Patient access has no criteria.
- [Get one user](/api/signals-api---clients/get-one-user): Without its secret, which is not retrievable.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_clients:read`. Project admins have every capability; client applications get no admin bypass.
- [Update a user](/api/signals-api---clients/update-a-user): Sets the name, webhook target, signing secret, events, event schema, reach or credential secret.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_webhooks:update`, `signals_events:read`. Project admins have every capability; client applications get no admin bypass.
- Only subscribers are changed through ovok. Their `role`, `eventSchema`, `clientId`, `secret` and `webhookSecret` can't be sent.
- It can reach any patient of your project without naming one, so it is for callers that reach every patient: project admins, manager or submitter apps, and anyone whose Patient access has no criteria.
- [Delete a user](/api/signals-api---clients/delete-a-user): Revokes the credential at once and drops its undelivered webhook events.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_webhooks:delete`. Project admins have every capability; client applications get no admin bypass.
- Only subscribers are deleted through ovok.
- It can reach any patient of your project without naming one, so it is for callers that reach every patient: project admins, manager or submitter apps, and anyone whose Patient access has no criteria.
- [Read the webhook endpoint](/api/signals-api---clients/read-the-webhook-endpoint): The URL, whether it is `enabled`, `failing` or `disabled` (and why), when its current run of failures began, and its newest delivered and failed attempts. Five days of failures with no delivery, or one 410, disable it.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_webhooks:read`. Project admins have every capability; client applications get no admin bypass.
- [Enable the webhook endpoint](/api/signals-api---clients/enable-the-webhook-endpoint): Resumes deliveries of new events. It re-queues nothing: events held while it was disabled go out only through `POST /api/events/redeliver?status=held`. Enabling an enabled endpoint changes nothing.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_webhooks:update`. Project admins have every capability; client applications get no admin bypass.
- It can reach any patient of your project without naming one, so it is for callers that reach every patient: project admins, manager or submitter apps, and anyone whose Patient access has no criteria.
- [Disable the webhook endpoint](/api/signals-api---clients/disable-the-webhook-endpoint): Stops deliveries. Queued events and new ones are held, not dropped, for a redeliver once it is enabled again. Disabling a disabled endpoint keeps its first reason.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_webhooks:update`. Project admins have every capability; client applications get no admin bypass.
- It can reach any patient of your project without naming one, so it is for callers that reach every patient: project admins, manager or submitter apps, and anyone whose Patient access has no criteria.
- [Send a test event](/api/signals-api---clients/send-a-test-event): Sends a signed `webhook.test` event (`{ event, subscriber, sentAt }`) now, with every header a real delivery carries, and answers what the receiver did. It is not queued, retried or listed, and it works on a disabled endpoint, so a receiver can be checked before it is enabled.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_webhooks:update`. Project admins have every capability; client applications get no admin bypass.
- It can reach any patient of your project without naming one, so it is for callers that reach every patient: project admins, manager or submitter apps, and anyone whose Patient access has no criteria.
- [Rotate the webhook signing secret](/api/signals-api---clients/rotate-the-webhook-signing-secret): Mints a new Standard Webhooks signing secret (`whsec_` and 32 random bytes) and returns it this once. Every delivery carries `webhook-id` and `webhook-timestamp`; once the subscriber has a secret it also carries `webhook-signature`, so a receiver verifies with the Standard Webhooks library. The secret it replaces keeps signing until `previousSecretExpiresAt` (24 h), so `webhook-signature` holds both and the receiver switches with no gap. Each rotation keeps only the secret it replaces, so rotate again only once the receiver uses the new one. The first call mints the subscriber's first secret. A MANAGER rotates any subscriber of its tenant; a SUBSCRIBER only its own.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_webhooks:update`. Project admins have every capability; client applications get no admin bypass.
- It can reach any patient of your project without naming one, so it is for callers that reach every patient: project admins, manager or submitter apps, and anyone whose Patient access has no criteria.
- Signals API - Events: Every webhook event queued, with how its delivery went, and sending it again: the safety net after a receiver outage.
- [List webhook events](/api/signals-api---events/list-webhook-events): Every event addressed to the caller, oldest first and keyset paged by `cursor`, each with its delivery status and payload: expired, failed and held ones too. An event is listed once it is two minutes old, so a page never moves past one a slower transaction has yet to commit. Filter by `types`, `status`, `patientId`, or (for a MANAGER) `subscriberId`.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_events:read`. Project admins have every capability; client applications get no admin bypass.
- If your access covers only some patients (anyone but project admins, manager or submitter apps, and anyone whose Patient access has no criteria), name them with `patientId`, each one you can read, at most 100. Signals then pages over just those patients, so pages and totals stay true. A patient is named by its Signals id, or by its Medplum Patient id where Signals takes a `refId`; ovok asks Signals which patient a Signals id is.
- [List a delivery's attempts](/api/signals-api---events/list-a-delivery-s-attempts): Each POST made for it, oldest first: when, how long, the status code or the error, and the first 2 KB of the answer.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_events:read`. Project admins have every capability; client applications get no admin bypass.
- It can reach any patient of your project without naming one, so it is for callers that reach every patient: project admins, manager or submitter apps, and anyone whose Patient access has no criteria.
- [Redeliver events](/api/signals-api---events/redeliver-events): Queues the oldest 1 000 deliveries in `status` (default `failed`) queued since `since` to be sent again, each with a fresh retry ladder: the same `webhook-id`, so receivers dedupe, and a new `webhook-timestamp`. An alert is sent whatever its age. Deliveries to a disabled endpoint, and ones being sent right now, are skipped. While it answers `queued: 1000`, call it again for the next ones.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_webhooks:update`. Project admins have every capability; client applications get no admin bypass.
- It can reach any patient of your project without naming one, so it is for callers that reach every patient: project admins, manager or submitter apps, and anyone whose Patient access has no criteria.
- [Redeliver one event](/api/signals-api---events/redeliver-one-event): Queues this delivery to be sent now, whatever its status, with a fresh retry ladder: the same `webhook-id` and a new `webhook-timestamp`. An alert is sent whatever its age.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_webhooks:update`. Project admins have every capability; client applications get no admin bypass.
- It can reach any patient of your project without naming one, so it is for callers that reach every patient: project admins, manager or submitter apps, and anyone whose Patient access has no criteria.
- Signals API - Health: Liveness plus the pipeline check that alarms when readings arrive and nothing alerts, or alerts queue and nothing is delivered.
- [Liveness](/api/signals-api---health/liveness): Unauthenticated, always 200: the HTTP status reports that the process is serving, not that its dependencies are well, because this route gates container liveness. `healthy` is false while the database does not answer (`db.ok`); page on it.
### Through ovok
- **Who can call it:** any caller with an ovok access token, or a client id and secret. It needs no capability.
- Signals API - Observations: Submitting readings. This is the hot path: one POST per reading (or a batch), judged by the patient's triggers in force (its own over its home's) before the response returns unless the tenant is in async mode.
- [Submit a reading by patient refId](/api/signals-api---observations/submit-a-reading-by-patient-refid): The patient is `patientRefId` in the body, else `subject.reference` (`Patient/`).
The body is a FHIR R4 Observation: `code`, `value[x]` (any of FHIR R4's eleven) or `dataAbsentReason`, and a combined device sample's vitals as `component[]` (each `code`, `value[x]`, optional `dataAbsentReason`). The clinical time is `effectiveDateTime`, `effectiveInstant` or `effectivePeriod.start`, else `issued`, else the server's receipt; `device` is `{reference, display}`; the first `identifier`'s `value` is stored as v1's `refId`. Other FHIR elements (`meta`, `extension`, `performer`, `interpretation`, `referenceRange`, …) are accepted and ignored. v1's `observedAt` (it wins over `effective[x]`), `refId`, string `dataAbsentReason` and `device.id` still work. A node with `dataAbsentReason` and no value is judged as carrying no value, and the rules read the reason as `absent`. `valueSampledData` is judged by its latest sample and stored whole, and the rules read every sample as `samples`. A `valueDateTime`, on the reading or a component, is judged and aggregated as epoch ms (`valueNumber`) and listed as sent. A `valueBoolean` is judged as 1 or 0. A `valueTime` is judged as ms since midnight, a `valuePeriod` as its length in ms (none while open or without an offset), a `valueRatio` as its quotient, and each is also kept as text (`valueString`); a `valueRange` is judged as the text `low..high`.
**History is never judged.** A reading whose `category` has a coding the tenant names as history (its `historyCategories`; the default pack's: `batch-data`, `statistics`, `presence-batch`, `derived`) is stored and deduplicated like any other, answered with no alerts, and never judged nor read by the rules.
The caller must be one of the patient's submitters, admins included: a known patient it is not attached to is a 403, an unknown one a 404 with type `PATIENT_NOT_FOUND`.
**Idempotent.** A reading is named by its first `identifier` (`system|value`), else by its clinical time, code, device and `refId`. A resend of one stored within the last hour is a 409 `DUPLICATE_READING` whose `data.observationId` is the stored reading, and is neither stored nor evaluated again. A reading with neither an identifier nor a clinical time is not deduplicated.
Concurrent requests are batched server-side. With `Prefer: respond-sync` evaluation finishes first and the reply adds `alertIds`, `alerts` (`[{id, code}]`), `resolvedAlertIds` and `ruleCoverage`; without it `mode` is `async` and alerts are delivered by webhook.
### Through ovok
- **Who can call it:** Submitter apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_observations:create`. Project admins have every capability; client applications get no admin bypass.
- If your access covers only some patients (anyone but project admins, manager or submitter apps, and anyone whose Patient access has no criteria), every patient the body names must be one you can update. A patient is named by its Signals id, or by its Medplum Patient id where Signals takes a `refId`; ovok asks Signals which patient a Signals id is.
- Each reading counts its patient as monitored for billing.
- [List a patient's readings](/api/signals-api---observations/list-a-patient-s-readings): Newest first by clinical time, in v1's envelope `{items, total, page, pageSize, totalPages}` with nest's `nextCursor`. `from`, `to` and `code` (comma-separated) filter as in v1; `category` (comma-separated) keeps the readings carrying any of those FHIR category codes. With `from`, a reading whose clinical time is more than an hour ahead of when it was stored is not found: the read skips the storage older than that, so it never waits on it. `pageSize` defaults to 10; above 200 it needs both `from` and `to`, and then reaches 10000. A page may start at most 10000 rows in; walk further with `cursor`.
Each item is a flat timeline entry (`code`, `valueNumber`, `observedAt`, `category`, `valueSampledData`, `components`, `effectiveStart`, `effectiveEnd`, `deviceId`, …) with v1's `_id`, `id`, `patient`, `valueQuantity` and `createdAt` beside it. `fields=code,valueNumber` returns exactly those keys instead.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_observations:read`. Project admins have every capability; client applications get no admin bypass.
- If your access covers only some patients (anyone but project admins, manager or submitter apps, and anyone whose Patient access has no criteria), the patient must be one you can read. A patient is named by its Signals id, or by its Medplum Patient id where Signals takes a `refId`; ovok asks Signals which patient a Signals id is.
- [Submit a reading for a patient id](/api/signals-api---observations/submit-a-reading-for-a-patient-id): `subject` is ignored: the path names the patient.
The body is a FHIR R4 Observation: `code`, `value[x]` (any of FHIR R4's eleven) or `dataAbsentReason`, and a combined device sample's vitals as `component[]` (each `code`, `value[x]`, optional `dataAbsentReason`). The clinical time is `effectiveDateTime`, `effectiveInstant` or `effectivePeriod.start`, else `issued`, else the server's receipt; `device` is `{reference, display}`; the first `identifier`'s `value` is stored as v1's `refId`. Other FHIR elements (`meta`, `extension`, `performer`, `interpretation`, `referenceRange`, …) are accepted and ignored. v1's `observedAt` (it wins over `effective[x]`), `refId`, string `dataAbsentReason` and `device.id` still work. A node with `dataAbsentReason` and no value is judged as carrying no value, and the rules read the reason as `absent`. `valueSampledData` is judged by its latest sample and stored whole, and the rules read every sample as `samples`. A `valueDateTime`, on the reading or a component, is judged and aggregated as epoch ms (`valueNumber`) and listed as sent. A `valueBoolean` is judged as 1 or 0. A `valueTime` is judged as ms since midnight, a `valuePeriod` as its length in ms (none while open or without an offset), a `valueRatio` as its quotient, and each is also kept as text (`valueString`); a `valueRange` is judged as the text `low..high`.
**History is never judged.** A reading whose `category` has a coding the tenant names as history (its `historyCategories`; the default pack's: `batch-data`, `statistics`, `presence-batch`, `derived`) is stored and deduplicated like any other, answered with no alerts, and never judged nor read by the rules.
The caller must be one of the patient's submitters, admins included: a known patient it is not attached to is a 403, an unknown one a 404 with type `PATIENT_NOT_FOUND`.
**Idempotent.** A reading is named by its first `identifier` (`system|value`), else by its clinical time, code, device and `refId`. A resend of one stored within the last hour is a 409 `DUPLICATE_READING` whose `data.observationId` is the stored reading, and is neither stored nor evaluated again. A reading with neither an identifier nor a clinical time is not deduplicated.
Concurrent requests are batched server-side. With `Prefer: respond-sync` evaluation finishes first and the reply adds `alertIds`, `alerts` (`[{id, code}]`), `resolvedAlertIds` and `ruleCoverage`; without it `mode` is `async` and alerts are delivered by webhook.
### Through ovok
- **Who can call it:** Submitter apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_observations:create`. Project admins have every capability; client applications get no admin bypass.
- If your access covers only some patients (anyone but project admins, manager or submitter apps, and anyone whose Patient access has no criteria), the patient must be one you can update. A patient is named by its Signals id, or by its Medplum Patient id where Signals takes a `refId`; ovok asks Signals which patient a Signals id is.
- Each reading counts its patient as monitored for billing.
- [Submit several readings for one patient](/api/signals-api---observations/submit-several-readings-for-one-patient): `{observations: []}`, each as `POST /api/observation/:patientId` takes it. In one transaction, evaluated in the order given. Answers v1's array, one entry per reading in request order: the reading as stored, or for a resend the 409 `DUPLICATE_READING` error body in its place. An array has nowhere to carry the ingest outcome, so a caller that needs `alertIds` posts them singly.
The body is a FHIR R4 Observation: `code`, `value[x]` (any of FHIR R4's eleven) or `dataAbsentReason`, and a combined device sample's vitals as `component[]` (each `code`, `value[x]`, optional `dataAbsentReason`). The clinical time is `effectiveDateTime`, `effectiveInstant` or `effectivePeriod.start`, else `issued`, else the server's receipt; `device` is `{reference, display}`; the first `identifier`'s `value` is stored as v1's `refId`. Other FHIR elements (`meta`, `extension`, `performer`, `interpretation`, `referenceRange`, …) are accepted and ignored. v1's `observedAt` (it wins over `effective[x]`), `refId`, string `dataAbsentReason` and `device.id` still work. A node with `dataAbsentReason` and no value is judged as carrying no value, and the rules read the reason as `absent`. `valueSampledData` is judged by its latest sample and stored whole, and the rules read every sample as `samples`. A `valueDateTime`, on the reading or a component, is judged and aggregated as epoch ms (`valueNumber`) and listed as sent. A `valueBoolean` is judged as 1 or 0. A `valueTime` is judged as ms since midnight, a `valuePeriod` as its length in ms (none while open or without an offset), a `valueRatio` as its quotient, and each is also kept as text (`valueString`); a `valueRange` is judged as the text `low..high`.
**History is never judged.** A reading whose `category` has a coding the tenant names as history (its `historyCategories`; the default pack's: `batch-data`, `statistics`, `presence-batch`, `derived`) is stored and deduplicated like any other, answered with no alerts, and never judged nor read by the rules.
The caller must be one of the patient's submitters, admins included: a known patient it is not attached to is a 403, an unknown one a 404 with type `PATIENT_NOT_FOUND`.
**Idempotent.** A reading is named by its first `identifier` (`system|value`), else by its clinical time, code, device and `refId`. A resend of one stored within the last hour is a 409 `DUPLICATE_READING` whose `data.observationId` is the stored reading, and is neither stored nor evaluated again. A reading with neither an identifier nor a clinical time is not deduplicated.
Concurrent requests are batched server-side. With `Prefer: respond-sync` evaluation finishes first and the reply adds `alertIds`, `alerts` (`[{id, code}]`), `resolvedAlertIds` and `ruleCoverage`; without it `mode` is `async` and alerts are delivered by webhook.
### Through ovok
- **Who can call it:** Submitter apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_observations:create`. Project admins have every capability; client applications get no admin bypass.
- If your access covers only some patients (anyone but project admins, manager or submitter apps, and anyone whose Patient access has no criteria), the patient must be one you can update. A patient is named by its Signals id, or by its Medplum Patient id where Signals takes a `refId`; ovok asks Signals which patient a Signals id is.
- Each reading counts its patient as monitored for billing.
- Signals API - Patient config: v1's config writes addressed by a list of patient ids. A trigger is stored as sent in the patient's settings, where it judges its code; one without logic is rejected and reported. Reads are POSTs here too — a thousand ids do not fit in a query string.
- [Write one config to many patients](/api/signals-api---patient-config/write-one-config-to-many-patients): Applies the same triggers, flags and metadata to every id named. An id may be the Signals id or the `refId`.
Unknown ids are reported in `misses[]` rather than failing the batch. `modifiedCount` counts patients where a row actually changed; `status.triggerWrites` gives the row counts behind it. Trigger `metadata` is MERGED and left untouched when absent.
Send `resolvedAt` (the instant the configuration was COMPUTED, not sent) to have this push refused rather than applied when the patient already carries a configuration resolved strictly later. Refusal is a silent no-op reported as `staleIgnoredCount`, never an error. Omitting it applies the push unconditionally.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_config:update`, `signals_silence:update`. Project admins have every capability; client applications get no admin bypass.
- A patient stamped with another project is refused.
- If your access covers only some patients (anyone but project admins, manager or submitter apps, and anyone whose Patient access has no criteria), every patient the body names must be one you can update. A patient is named by its Signals id, or by its Medplum Patient id where Signals takes a `refId`; ovok asks Signals which patient a Signals id is.
- [Write a different config to each of many patients](/api/signals-api---patient-config/write-a-different-config-to-each-of-many-patients): The per-patient form, each entry with its own triggers, flags, `metadata` and `resolvedAt`. `results[]` has v1's `{id, matched, modified}` per entry; `staleIgnored` and `misses` are additions.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_config:update`, `signals_silence:update`. Project admins have every capability; client applications get no admin bypass.
- A patient stamped with another project is refused.
- If your access covers only some patients (anyone but project admins, manager or submitter apps, and anyone whose Patient access has no criteria), every patient the body names must be one you can update. A patient is named by its Signals id, or by its Medplum Patient id where Signals takes a `refId`; ovok asks Signals which patient a Signals id is.
- [Read the config status of many patients](/api/signals-api---patient-config/read-the-config-status-of-many-patients): Each patient's `configUpdatedAt` and trigger codes, so a caller can confirm a push landed without re-reading every trigger; `triggerFingerprints` adds what each rule says.
Exactly one row per requested id, in the order asked. An id that resolves to nothing answers `refId: null` with no codes, as v1 did.
A POST because the id list is the request; it writes nothing.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_config:read`. Project admins have every capability; client applications get no admin bypass.
- If your access covers only some patients (anyone but project admins, manager or submitter apps, and anyone whose Patient access has no criteria), every patient the body names must be one you can read. A patient is named by its Signals id, or by its Medplum Patient id where Signals takes a `refId`; ovok asks Signals which patient a Signals id is.
- Signals API - Patients: Patients and their alerting flags. `refId` is the caller's own identifier.
- [List patients](/api/signals-api---patients/list-patients-2): Scoped to what the caller may see: a MANAGER gets the tenant, a SUBMITTER or SUBSCRIBER only the patients it is attached to. Newest first.
v1's paginate envelope, driven by `page` and `limit` (default 10). A page may start at most 10000 rows in; past that, walk with the keyset `cursor`: pass `nextCursor` back verbatim, and `null` means the last page.
### Through ovok
- **Who can call it:** Manager apps and Submitter apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_patients:read`. Project admins have every capability; client applications get no admin bypass.
- If your access covers only some patients (anyone but project admins, manager or submitter apps, and anyone whose Patient access has no criteria), name them with `ids`, each one you can read, at most 100. Signals then pages over just those patients, so pages and totals stay true. A patient is named by its Signals id, or by its Medplum Patient id where Signals takes a `refId`; ovok asks Signals which patient a Signals id is.
- [Create patients](/api/signals-api---patients/create-patients): Upserts on `refId`: posting a patient that already exists updates it rather than duplicating it, so a replayed provisioning run is safe. Each entry's `config` — flags and triggers — is applied in the same call, and `metadata` is merged.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_patients:update`. Project admins have every capability; client applications get no admin bypass.
- ovok adds your project's submitter to each new patient and stamps `metadata.project` with your project. Sending `config` also needs `signals_config:update` and `signals_silence:update`. New patients count toward your plan's monitored-patient cap.
- If your access covers only some patients (anyone but project admins, manager or submitter apps, and anyone whose Patient access has no criteria), every patient the body names must be one you can update. A patient is named by its Signals id, or by its Medplum Patient id where Signals takes a `refId`; ovok asks Signals which patient a Signals id is.
- [Resolve refIds to Signals ids](/api/signals-api---patients/resolve-refids-to-signals-ids): An object from each `patientRefId` this tenant holds to its Signals id. A refId the tenant does not hold is left out rather than failing the request.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_patients:read`. Project admins have every capability; client applications get no admin bypass.
- If your access covers only some patients (anyone but project admins, manager or submitter apps, and anyone whose Patient access has no criteria), name them with `patientRefId`, each one you can read, at most 100. Signals then pages over just those patients, so pages and totals stay true. A patient is named by its Signals id, or by its Medplum Patient id where Signals takes a `refId`; ovok asks Signals which patient a Signals id is.
- [Get one patient](/api/signals-api---patients/get-one-patient): The patient with its config, triggers keyed by code. A SUBMITTER or SUBSCRIBER reads only patients it is attached to.
### Through ovok
- **Who can call it:** Manager apps and Submitter apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_patients:read`. Project admins have every capability; client applications get no admin bypass.
- If your access covers only some patients (anyone but project admins, manager or submitter apps, and anyone whose Patient access has no criteria), the patient must be one you can read. A patient is named by its Signals id, or by its Medplum Patient id where Signals takes a `refId`; ovok asks Signals which patient a Signals id is.
- [Update a patient](/api/signals-api---patients/update-a-patient): Only the fields present are changed; `metadata` is merged, not replaced. Answered with the v1 document.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_patients:update`. Project admins have every capability; client applications get no admin bypass.
- When you send `submitters` or `metadata`, ovok keeps your project's submitter and stamp in them. Sending `alertsPaused`, `alertIfEmpty`, `alertIfNote` or `alertInactiveDays` also needs `signals_config:update` and `signals_silence:update`.
- If your access covers only some patients (anyone but project admins, manager or submitter apps, and anyone whose Patient access has no criteria), the patient, and one a `refId` in the body names, must be ones you can update. A patient is named by its Signals id, or by its Medplum Patient id where Signals takes a `refId`; ovok asks Signals which patient a Signals id is.
- [Delete a patient](/api/signals-api---patients/delete-a-patient): Deletes the patient with its triggers, alerts, episodes and readings. This can't be undone through the API.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_patients:delete`. Project admins have every capability; client applications get no admin bypass.
- If your access covers only some patients (anyone but project admins, manager or submitter apps, and anyone whose Patient access has no criteria), the patient must be one you can delete. A patient is named by its Signals id, or by its Medplum Patient id where Signals takes a `refId`; ovok asks Signals which patient a Signals id is.
- [List a submitter's patients](/api/signals-api---patients/list-a-submitter-s-patients): The ids of the patients in this tenant the submitter may post readings for.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_associations:read`. Project admins have every capability; client applications get no admin bypass.
- It can reach any patient of your project without naming one, so it is for callers that reach every patient: project admins, manager or submitter apps, and anyone whose Patient access has no criteria.
- [Attach patients to a submitter](/api/signals-api---patients/attach-patients-to-a-submitter): Grants the client permission to post readings for those patients. Unknown patient ids are skipped, so a caller can re-post a superset of ids without a 404.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_associations:update`. Project admins have every capability; client applications get no admin bypass.
- If your access covers only some patients (anyone but project admins, manager or submitter apps, and anyone whose Patient access has no criteria), every patient the body names must be one you can update. A patient is named by its Signals id, or by its Medplum Patient id where Signals takes a `refId`; ovok asks Signals which patient a Signals id is.
- [Detach a patient from a submitter](/api/signals-api---patients/detach-a-patient-from-a-submitter): The submitter may no longer post readings for the patient.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_associations:update`. Project admins have every capability; client applications get no admin bypass.
- Your project's own submitter stays attached to its patients.
- If your access covers only some patients (anyone but project admins, manager or submitter apps, and anyone whose Patient access has no criteria), the patient must be one you can update. A patient is named by its Signals id, or by its Medplum Patient id where Signals takes a `refId`; ovok asks Signals which patient a Signals id is.
- [List a subscriber's patients](/api/signals-api---patients/list-a-subscriber-s-patients): The ids of the patients in this tenant the subscriber reads and receives alerts for.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_associations:read`. Project admins have every capability; client applications get no admin bypass.
- It can reach any patient of your project without naming one, so it is for callers that reach every patient: project admins, manager or submitter apps, and anyone whose Patient access has no criteria.
- [Attach patients to a subscriber](/api/signals-api---patients/attach-patients-to-a-subscriber): Grants the client permission to read those patients' records and receive their alert webhooks. Unknown patient ids are skipped, so a caller can re-post a superset of ids without a 404.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_associations:update`. Project admins have every capability; client applications get no admin bypass.
- If your access covers only some patients (anyone but project admins, manager or submitter apps, and anyone whose Patient access has no criteria), every patient the body names must be one you can update. A patient is named by its Signals id, or by its Medplum Patient id where Signals takes a `refId`; ovok asks Signals which patient a Signals id is.
- [Detach a patient from a subscriber](/api/signals-api---patients/detach-a-patient-from-a-subscriber): The subscriber no longer reads the patient's records or receives its alerts.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_associations:update`. Project admins have every capability; client applications get no admin bypass.
- If your access covers only some patients (anyone but project admins, manager or submitter apps, and anyone whose Patient access has no criteria), the patient must be one you can update. A patient is named by its Signals id, or by its Medplum Patient id where Signals takes a `refId`; ovok asks Signals which patient a Signals id is.
- Signals API - Settings: What the triggers judge by, per org (tenant and project) and per patient: the triggers themselves, bands per code and the out-of-bed limit. Opaque to Signals beyond `triggers`: the triggers give the other keys their meaning.
- [Get a patient's own rule settings](/api/signals-api---settings/get-a-patient-s-own-rule-settings): `{}` when none were written.
Signals stores `settings` as given and reads it only through the triggers it holds (`triggers`, the home's and the resident's own): what each other key means is theirs, so the object is checked for shape and size only. The default pack reads `bands` — per code `{ low?, high? }`, `{ mode: 'baseline', pct, low?, high? }` (outside either band fires; a patient's own baseline band with no sides of its own is judged alone) or `"NO_THRESHOLD"` — and `outOfBed`: `{ limitSeconds, enabled?, returnSeconds?, window?: { start: 'HH:MM', end: 'HH:MM', timeZone } }`. A patient's own key wins over the org's; extra keys are ignored. See tech/RULES-ENGINE.md.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_config:read`. Project admins have every capability; client applications get no admin bypass.
- If your access covers only some patients (anyone but project admins, manager or submitter apps, and anyone whose Patient access has no criteria), the patient must be one you can read. A patient is named by its Signals id, or by its Medplum Patient id where Signals takes a `refId`; ovok asks Signals which patient a Signals id is.
- [Replace a patient's own rule settings](/api/signals-api---settings/replace-a-patient-s-own-rule-settings): Replaced whole. The triggers read them beside the org's, as `settings.individual`. Audited as `UPDATE_PATIENT_SETTINGS`.
For each patient the write reaches, judged by its own settings over the org's: a band changed ends the episode judged under the old one `THRESHOLD_CHANGED`; a code switched off (its band `"NO_THRESHOLD"`, or out of bed `enabled: false`) has its active alert resolved and its open episode closed `THRESHOLD_CHANGED` at the save, the other codes' left alone; an acknowledged out-of-bed absence, whose episode the ack closed, has its still-active alert resolved; a band deleted closes nothing.
A body whose `settings` has no `triggers` key keeps the stored `triggers` (the home defaults seeded by Signals, or triggers pushed through the config routes); a body that sends `triggers`, `{}` included, replaces them.
The body may carry `resolvedAt` (ISO 8601), when the caller resolved this write: it lands only over a stored write resolved earlier, or stored without one, and is otherwise refused 409 `STALE_SETTINGS` with nothing written or audited. A write without `resolvedAt` always lands and keeps the stored stamp.
Signals stores `settings` as given and reads it only through the triggers it holds (`triggers`, the home's and the resident's own): what each other key means is theirs, so the object is checked for shape and size only. The default pack reads `bands` — per code `{ low?, high? }`, `{ mode: 'baseline', pct, low?, high? }` (outside either band fires; a patient's own baseline band with no sides of its own is judged alone) or `"NO_THRESHOLD"` — and `outOfBed`: `{ limitSeconds, enabled?, returnSeconds?, window?: { start: 'HH:MM', end: 'HH:MM', timeZone } }`. A patient's own key wins over the org's; extra keys are ignored. See tech/RULES-ENGINE.md.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_config:update`, `signals_silence:update`. Project admins have every capability; client applications get no admin bypass.
- `resolvedAt` may be at most 5 minutes ahead of now.
- If your access covers only some patients (anyone but project admins, manager or submitter apps, and anyone whose Patient access has no criteria), the patient must be one you can update. A patient is named by its Signals id, or by its Medplum Patient id where Signals takes a `refId`; ovok asks Signals which patient a Signals id is.
- Signals API - Tenants: Tenant-level feature switches.
- [Get the caller's tenant](/api/signals-api---tenants/get-the-caller-s-tenant): The tenant is resolved from the credential; there is no id to pass. `orgSettings` is its tenant-wide rule settings, `{}` when none were written.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_config:read`. Project admins have every capability; client applications get no admin bypass.
- [Update tenant settings](/api/signals-api---tenants/update-tenant-settings): `settings` is merged, so a switch left out is left alone. Takes effect on the next request — except `throttleLimit`, which each replica caches for up to a minute.
`episodicAlerts` gates episode recording and the notification feed. `synchronousMode` makes ingest return before evaluating. `throttleLimit` overrides THROTTLE_LIMIT for this tenant; `0` exempts it.
`informationalCodes`, `historyCategories` and `feedCodes` (lists of strings, none by default): the codes the tenant's triggers consume and never judge (no no-verdict, and the `*` trigger skips them), the reading categories stored and never judged, and the codes the notification feed lists by default (none: every code). A new tenant is given the default pack's lists, and a tenant that states no `historyCategories` keeps the default pack's.
`ruleSet` is deprecated and ignored: readings are judged by the triggers in the rule settings, the tenant's (`orgSettings`) and each patient's own (`/api/patient/{id}/settings`).
`orgSettings` is the tenant's rule settings, which replace `/api/settings/org`: what the triggers judge by, the triggers themselves included. It is merged key by key: a key sent replaces the stored one whole, `null` removes it, and a key left out is kept. Its first write is given the default pack when it sends no `triggers`. Audited as `UPDATE_ORG_SETTINGS`. A band it changes ends the episodes judged under the old one, and a code it switches off (its band `"NO_THRESHOLD"`, or out of bed `enabled: false`) has its active alerts resolved and its open episodes closed `THRESHOLD_CHANGED` at the save, for every patient judged by the tenant's settings (an acknowledged out-of-bed absence, whose episode the ack closed, has its still-active alert resolved); a band deleted closes nothing. A tenant whose only org settings are one project's, with no patient stamped with another project, has them made the tenant's first, so the write reaches the patients they served.
`resolvedAt` (ISO 8601) is when the caller resolved `orgSettings`: it lands only over a write resolved earlier, or stored without one, and is otherwise refused 409 `STALE_SETTINGS` with nothing written. With `ifAbsent=true` each key of `orgSettings` is written only where the stored settings lack it, so a default seeded at enrolment cannot overwrite a concurrent save.
### Through ovok
- **Who can call it:** Manager apps, and users whose access policy grants the capabilities.
- **Capabilities, all required:** `signals_config:update`, `signals_silence:update`. Project admins have every capability; client applications get no admin bypass.
- Only `orgSettings`, `resolvedAt` and `settings.episodicAlerts` are written through ovok. Turning `episodicAlerts` off stops Signals recording episodes, which empties episode feeds.
- It can reach any patient of your project without naming one, so it is for callers that reach every patient: project admins, manager or submitter apps, and anyone whose Patient access has no criteria.
- Sleepiz
- [Backfill missed Sleepiz nights](/api/sleepiz/backfill-missed-sleepiz-nights): Refills Sleepiz nights that the live push never delivered. Ovok pulls each night from the Sleepiz public API for one device and sends it through the same ingest path as live data.
**Auth:** Bearer token. The caller must be a project admin.
**Scope:** The caller's project (from the token) and its sub-projects. The device must belong to one of them. The route takes no project parameter.
## Behaviour
- The body is strict: `serialNumber` (6 to 10 digits), `from` and `to` (`YYYY-MM-DD`). Unknown fields are rejected.
- `from` to `to` is inclusive, `from` must not be after `to`, `to` must be yesterday (UTC) or earlier, and the range is at most 7 nights. Longer gaps need several calls.
- The device is looked up by its serial. It must be a Sleepiz serial, must belong to your project or one of its sub-projects (403 otherwise), and must have an assigned resident (409 otherwise). Assign the resident and call again. A serial held by devices in more than one project is refused with 409 too: ingest routes its readings to none of them.
- Nights are matched to the device's patient deployments in Sleepiz. Each night in the response has an `outcome`: `posted`, `no_recording`, `no_deployment` or `error`. One night failing does not stop the others.
- **`posted` means queued, not stored.** Ovok queues the night's Observations through the live ingest router and nothing is written to Medplum during the request. `jobIds` lists the queued jobs and `missingBlocks` names data Sleepiz had no samples for.
- Re-running the same device and range is safe. The queue job ids are deterministic and the Observation write is conditional on its event id, so no duplicates appear.
- The route answers 503 when the Sleepiz credentials are not configured on the server, and 502 when the Sleepiz API cannot be reached.
- **Last night needs no call.** Every day at 11:00 UTC ovok runs the same refill for yesterday's night on every device that has a resident and no recording of that night yet, except the stakeholder beds. Use this route for older nights.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/sleepiz/backfill' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{ "serialNumber": "", "from": "2026-01-01", "to": "2026-01-03" }'
```
- Slim
- Slim/Change History
- [List change history](/api/slim/change-history/list-change-history): Lists the recorded changes to thresholds, locations, zones, devices, residents, and users and their roles in your project, newest first. Use it for the resident and administration change-history screens.
**Auth:** bearer token. The caller must be a practitioner, an admin or a System Owner, and needs `Provenance:read`. Only the Admin and System Owner access policies carry it, so other users get no change history.
**Scope:** the caller's project (from the token). The rows are read with your token, so you only see changes recorded in your project.
## Behaviour
- Each row is one recorded change. `details.key` names the change and `details.values` holds the values to put into its message.
- Rows are sorted by recording time, newest first. `sort=asc` returns the same rows oldest first. Rows recorded in the same millisecond keep one fixed order, so paging never skips or repeats a row.
- Without `startDate`, the list starts 30 days before now. `endDate` is optional. Both bounds are inclusive.
- `types` limits the rows to the given change types. Omit it to get every type. Repeat the parameter to pass several types.
- `scope=global` keeps only project-wide changes: organization threshold edits, device, location and zone lifecycle changes, and user and role changes. `scope=all` (the default) returns every row.
- `types=Access` lists the user and role changes: a user invited (`userInvited`), renamed (`userRenamed`), moved to another role (`userRoleChanged`) or removed (`userRemoved`), and a role created (`roleCreated`), given other permissions (`rolePermissionsChanged`) or deleted (`roleDeleted`). They name users and roles by name and concern no resident.
- `userId`, `residentId`, `deviceId`, `locationId` and `zoneId` are ids, not references. Each takes several ids, repeated or comma-separated, and matches a row concerning any of them. Different filters combine with AND. `userId` matches the practitioner who made the change. A value that is not an id answers 422.
- `relatedTo` takes typed references (`Patient/`, `Device/`, `Location/`, `Organization/`), repeated or comma-separated, and returns every row concerning any of them, once each.
- `actions` keeps the kinds of change you name: `created`, `deleted`, `renamed`, `moved`, `assigned`, `unassigned`, `discharged`, `reactivated`, `thresholdChanged`. Rows recorded before this filter shipped carry no kind, so they never match it.
- `resident` on a row is the resident it concerns, `{patientId, residentId}`, or null when it concerns none. `residentId` inside it is their Resident ID, or null when they have none: show a placeholder, never a name. The flat `residentId` carries the same Resident ID, or is empty. Neither is ever a name or an internal id.
- Threshold and Signals settings changes made through the Signals API are listed as Threshold changes, with the reason given.
- Changes a device makes itself, such as a baseline re-push after a new 7-day mean, are never listed, and never counted in `total`.
- Unknown query parameters are ignored.
- `page` is zero-based and `count` defaults to 50, maximum 100. `total` counts only rows the list can return, and never exceeds 10,000. Pages from offset 10,000 on come back empty, so narrow the filters or the date range to reach older rows.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/change-history?types=Threshold&types=Resident&page=0&count=50' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- Slim/Device
- [List projects for device assignment](/api/slim/device/list-projects-for-device-assignment): Lists the projects you can move a device into: your own project, its parent project and its sub-projects.
**Auth:** bearer token. The caller must be a practitioner, an admin or a System Owner, and needs `Device:update`.
**Scope:** the caller's project (from the token) and its parent and sub-projects.
## Behaviour
- Each entry is `{ project: { id, name } }`. `name` is an empty string when the project has none.
- `total` is the number of projects returned.
- Read-only.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/device/assign/project' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Move device to another project](/api/slim/device/move-device-to-another-project): Moves a device into another project in your project tree, and optionally places it in a location there.
**Auth:** bearer token. The caller must be a practitioner, an admin or a System Owner, and needs `Device:update`.
**Scope:** the device must be in the caller's project or one of its sub-projects. The target `projectId` must be your project, its parent or one of its sub-projects.
## Behaviour
- The device's resident, location and zone are removed before the move, because those references belong to the old project.
- If a resident was assigned, their active out-of-bed episode is closed and their open device use record is ended, in the same write.
- When you send `locationId`, the device is placed in that location. The location must be in the target project.
- The move is written in one transaction. If it fails, nothing is written.
- Moving a device to the project it is already in writes nothing and still succeeds.
- The response is `{ success, deviceId, projectId, locationId }`. `locationId` is `null` when you sent none.
## Example
```bash
curl -X PUT 'https://api.sandbox.ovok.com/v1/slim/device/assign/project' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{"deviceId": "c3499c27-2973-0a7f-807e-fb8676a92dcb", "projectId": "87ea5dfc-8b8e-384d-8489-79496e706390"}'
```
- [List devices](/api/slim/device/list-devices): Lists the devices in your project with their zone, location and resident names. Use it for device directory and administration screens.
**Auth:** bearer token. The caller must be a practitioner, an admin or a System Owner, and needs `Device:search` and `Device:read`.
**Scope:** the caller's project (from the token).
## Behaviour
- Every device in the project is listed, whether or not it has a location or resident.
- `id` is the Device resource id. `thingsboardId` is the device identifier that telemetry is keyed by.
- `status` is `active` when the device's live telemetry reports it active, otherwise `inactive`.
- `lastActivityTime` is the latest telemetry activity time, or the device's last-seen time when telemetry has none.
- `search` matches device, location and zone names. `patientSearch` matches resident names. Both are case-insensitive substring matches.
- `organizationId`, `locationId`, `patientId` and `active` filter on exact values.
- `orderBy` is `ASC` or `DESC` in any case, default `ASC`. Rows with an empty sort value sort last in `ASC` order and first in `DESC` order.
- `page` is zero-based. `count` defaults to 20, maximum 100. `total` is the number of rows after filtering.
- The device structure is cached for up to 5 minutes. Writes made through the Slim routes refresh it sooner.
- If the device list cannot be built and no earlier complete list exists, the route returns 503. Retry shortly.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/device?page=0&count=20&sortBy=deviceName&orderBy=ASC' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Create device](/api/slim/device/create-device): Creates a device in your project and, optionally, assigns it to a zone, a location and a resident in the same write.
**Auth:** bearer token. The caller must be a practitioner, an admin or a System Owner, and needs `Device:create`.
**Scope:** the device is created in the caller's project (from the token).
## Behaviour
- `name` is stored, trimmed, as the device's user-friendly name and as its official device identifier.
- A `name` whose serial a device of your project already holds is refused with 422, in any spelling: spaces around it or its `/`, other case, with or without the manufacturer prefix ingest gives the serial (`Sleepiz/30QD24001`, `sleepiz/30qd24001` and `30QD24001` hold one serial). Ingest finds a device by its serial, so one serial belongs to one device.
- A `name` whose serial a device of another customer already holds is refused with 409, in the same spellings. Ingest routes readings by serial, and cannot tell two customers' devices with one serial apart.
- `thingsboardId` is stored as a second identifier. When you omit it, Ovok generates a random UUID for it.
- `organizationId`, `locationId` and `patientId` are optional. Each one you send is assigned in the same transaction as the create. If any part fails, nothing is written.
- A device in a room is in the room's zone. An `organizationId` that differs from the zone of the device's room (the room you send, or the one it is in) answers 422 with `path: ["organizationId"]` and changes nothing: move the room to that zone, or the device to a room in it. A device with no room, or in a room with no zone, takes any zone. A zone change re-zones only the device, never its room, the room's other devices or a resident.
- Assigning a resident moves them: the resident is removed from every other device they hold, and their active out-of-bed episode is closed.
- Assigning a resident who is not active (`discharged`, `discharged-recent` or `archived`) is refused with 422 and nothing is created: reactivate them first.
- The device starts with status `active`.
- The create is recorded in the change history.
- The response is the same as `GET /v1/slim/device/{id}` for the new device.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/slim/device' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{"name": "Sleepiz/3001000", "locationId": "c3499c27-2973-0a7f-807e-fb8676a92dcb"}'
```
- [List live device rows](/api/slim/device/list-live-device-rows): Lists live device rows for the dashboard's monitoring views: each device with its resident, location, zone, current telemetry and alerts.
**Auth:** bearer token. The caller must be a practitioner, an admin or a System Owner, and needs `search` and `read` on `Patient`, `Device`, `Location`, `Observation` and `Organization`.
**Scope:** the caller's project (from the token).
## Behaviour
- By default only devices with a resident assigned are returned. Set `includeUnassigned=true` to include devices without a resident.
- `organizationId` takes a zone id, `assigned` or `unassigned`. A zone id also matches devices in its child zones.
- `deviceId` restricts the result to one device, matched against the device identifier.
- `effectiveSleepStatus` filters on the device's current sleep status.
- `search` matches resident nickname and name, location name, zone name, and device name, identifier and serial. The resident, location and zone names match fuzzily; the device fields match only as a case-insensitive substring, so a full device name finds that device alone.
- `orderBy` is `ASC` or `DESC` in any case, default `ASC`.
- `residentState` is accepted but ignored.
- A row's `patient` carries the resident's `id`, `name`, `meta` and `extension` only, not the whole FHIR Patient.
- Telemetry is read fresh on every request. The device structure is cached and refreshed when you write through the Slim routes.
- `page` is zero-based. `count` defaults to 20, maximum 100.
- For polling only the telemetry and alerts, use `GET /v1/slim/device/live/telemetry` with the same query.
- The route returns 503 when the server is at capacity for live reads or the device list cannot be built. Retry shortly.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/device/live?page=0&count=20&sortBy=locationName&orderBy=ASC' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [List live telemetry and alerts by device](/api/slim/device/list-live-telemetry-and-alerts-by-device): Returns the telemetry and alerts for one page of live devices, keyed by device. Use it to poll the values on a page you already loaded with `GET /v1/slim/device/live`.
**Auth:** bearer token. The caller must be a practitioner, an admin or a System Owner, and needs `search` and `read` on `Patient`, `Device`, `Location`, `Observation` and `Organization`.
**Scope:** the caller's project (from the token).
## Behaviour
- Takes the same query parameters as `GET /v1/slim/device/live` and selects the same page of devices in the same order.
- `telemetry` and `alerts` are keyed by the device identifier (for example `Sleepiz/3001000`), not by the Device resource id.
- `alerts` has an entry for every device on the page. `telemetry` has an entry only for devices with telemetry.
- Each telemetry entry carries the room readings `roomTemperature` (°C), `humidity` (%), `soundLevel` (dB), `infrared`, `illuminance` (lux), `airQualityIndex`, `tvoc` (ppb) and `eco2` (ppm) as `{ value, ts }`. They are `null` until the device reports one, and again when it has sent none for 30 minutes. They do not depend on whether the resident is in bed.
- `total`, `page` and `count` describe the page, as on `/live`.
- Telemetry is read fresh on every request.
- The route returns 503 when the server is at capacity for live reads or the device list cannot be built. Retry shortly.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/device/live/telemetry?page=0&count=20' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [List live device rows (lean)](/api/slim/device/list-live-device-rows-lean): Lists live device rows for the dashboard's monitoring views, with only the fields the dashboard renders: each device with its resident, room, zone, current readings and alert colours.
**Auth:** bearer token. The caller must be a practitioner, an admin or a System Owner, and needs `search` and `read` on `Patient`, `Device`, `Location`, `Observation` and `Organization`.
**Scope:** the caller's project (from the token) and its sub-projects.
## Behaviour
- The same query, filters, search, sort and paging as `GET /v1/slim/device/live`. The difference is where the rows come from: only the Devices are read, with the resident, room and zone context every Slim write stores on them, instead of a sweep of every Patient, Location and Organization of the project.
- Every field is present; an absent value is `null`.
- The resident, room and zone values are the copies stored on the Device, so what a row shows follows the Device's project and access, not a fresh read of the Patient. Each backend fills in devices no Slim write has touched once per build, after it boots; until then such a device answers `residentState: null` and no zone.
- `id` is the Medplum Device id. `GET /v2/slim/device/live/telemetry` keys its readings by it.
- The device structure is cached and refreshed when you write through the Slim routes. Readings and alerts are read fresh on every request.
- `page` is zero-based. `count` defaults to 20, maximum 100.
- The route returns 503 when the server is at capacity for live reads or the device list cannot be built. Retry shortly.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v2/slim/device/live?page=0&count=20&sortBy=locationName&orderBy=ASC' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [List live telemetry and alerts by device (lean)](/api/slim/device/list-live-telemetry-and-alerts-by-device-lean): Lists the current readings and alert colours of one page of live devices, keyed by Medplum Device id. Poll it with the query you sent to `GET /v2/slim/device/live` to refresh the vitals without reloading the rows.
**Auth:** bearer token. The caller must be a practitioner, an admin or a System Owner, and needs `search` and `read` on `Patient`, `Device`, `Location`, `Observation` and `Organization`.
**Scope:** the caller's project (from the token) and its sub-projects.
## Behaviour
- Takes the same query as `GET /v2/slim/device/live` and answers for the same page of devices.
- `resources` maps each Device id to its `telemetry` (`null` when the device has sent nothing recent) and `alerts`.
- Readings and alerts are read fresh on every request.
- The route returns 503 when the server is at capacity for live reads. Retry shortly.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v2/slim/device/live/telemetry?page=0&count=20&sortBy=locationName&orderBy=ASC' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Resolve how ingest routes device serials](/api/slim/device/resolve-how-ingest-routes-device-serials): For each serial you send, returns the manufacturer and model that ingest files it under, and whether ingest accepts the serial at all. Use it to check serials before you create devices or send data.
**Auth:** bearer token. The caller must be a practitioner, an admin or a System Owner, and needs `Device:read`.
## Behaviour
- Send 1 to 500 serials, each at most 64 characters.
- Rows come back in the order you sent them.
- `routable: false` means ingest rejects the serial with `400`. `manufacturer` and `model` are then `null`.
- The serial alone decides the result. A manufacturer that ingest only accepts when named explicitly, such as Masimo, is never inferred from a serial.
- Nothing is read or written. The result is computed from the serial only.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/slim/device/routing' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{"serials": ["3001000", "12,34"]}'
```
```json
{
"resources": [
{ "serial": "3001000", "manufacturer": "Sleepiz", "model": "OnePlus", "routable": true },
{ "serial": "12,34", "manufacturer": null, "model": null, "routable": false }
]
}
```
- [Get device](/api/slim/device/get-device): Returns one device with its zone, location and resident assignments and its current connection status. Use it to show or edit a single device.
**Auth:** bearer token. The caller must be a practitioner, an admin or a System Owner, and needs `Device:read`.
**Scope:** the device is read with your token, so Medplum's access policy limits which devices you can read.
## Behaviour
- `organizationId` is the zone of the device's location. When the device has no location, it falls back to the device's own owner organization.
- `organizationName`, `locationName` and `patientName` are display names. Each is `null` when the resource has no name that can be shown.
- `status` is `active` only when the device was last seen recently enough. A device whose last-seen time is stale reads `inactive`, even if it was never marked offline. A device without an identifier reports its stored FHIR status.
- `statusReason` explains an `inactive` status when the platform recorded why.
- `floor`, `room` and `bed` come from the address of the device's location.
- Read-only. Nothing is written.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/device/c3499c27-2973-0a7f-807e-fb8676a92dcb' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Delete device](/api/slim/device/delete-device): Deletes a device from your project.
**Auth:** bearer token. The caller must be a practitioner, an admin or a System Owner, and needs `Device:delete`. System Owners cannot delete devices.
**Scope:** the device must belong to the caller's project (from the token).
## Behaviour
- The Device resource is deleted. This cannot be undone.
- If a resident is assigned, they are unassigned and their active out-of-bed episode is closed in the same transaction.
- The device's use history is updated in the same transaction.
- The delete is recorded in the change history.
- The response has no body.
## Example
```bash
curl -X DELETE 'https://api.sandbox.ovok.com/v1/slim/device/c3499c27-2973-0a7f-807e-fb8676a92dcb' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Update device](/api/slim/device/update-device): Updates a device's name and identifier, and assigns or unassigns its zone, location and resident.
**Auth:** bearer token. The caller must be a practitioner, an admin or a System Owner, and needs `Device:update`.
**Scope:** the device must belong to the caller's project (from the token).
## Behaviour
- `name` is required and replaces the device's user-friendly name, trimmed. Renaming the device to a serial that a device of another customer already holds is refused with 409, in any spelling.
- Renaming the device to a serial another device of your project holds is refused with 422, in any spelling: spaces around it or its `/`, other case, with or without the manufacturer prefix ingest gives the serial. A device may take another spelling of its own serial.
- For `organizationId`, `locationId` and `patientId`: omit the field to leave it unchanged, send `null` to unassign, or send a UUID to assign.
- A device in a room is in the room's zone. An `organizationId` that differs from the zone of the device's room (the room you send, or the one it is in) answers 422 with `path: ["organizationId"]` and changes nothing: move the room to that zone, or the device to a room in it. A device with no room, or in a room with no zone, takes any zone. A zone change re-zones only the device, never its room, the room's other devices or a resident.
- Assigning a resident moves them: the resident is removed from every other device they hold. The resident who was on this device before loses it. Each resident who loses a device has their active out-of-bed episode closed in the same transaction.
- Assigning a resident who is not active (`discharged`, `discharged-recent` or `archived`) is refused with 422 and changes nothing: reactivate them first. Unassigning one (`patientId: null`) is allowed.
- All changes are written in one transaction. If any part fails, nothing is written.
- Sending values the device already has is not an error, and writes nothing.
- Resident assignment changes, renames, identifier changes and zone changes are recorded in the change history.
- The response is the same as `GET /v1/slim/device/{id}` after the update.
## Example
```bash
curl -X PUT 'https://api.sandbox.ovok.com/v1/slim/device/c3499c27-2973-0a7f-807e-fb8676a92dcb' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{"name": "Sleepiz/3001000", "patientId": null}'
```
- Slim/Export
- [Create night report PDF](/api/slim/export/create-night-report-pdf): Creates a night report PDF for one resident and stores it. Optionally emails the PDF. Use it for the report download and email feature on the dashboard.
**Auth:** bearer token. The caller must be a practitioner, an admin or a System Owner, and needs `search` and `read` on `Patient`, `Device` and `Observation`.
**Scope:** the caller's project (from the token). The resident is read with your token, so Medplum's access policy limits which residents you can export.
## Behaviour
- `code` must be `TRENDS_PDF_EXPORT`. Any other value is rejected with `400`.
- The report layout comes from the export template content for `code` and `language`. A template in your project wins over one in its parent project. The route returns `400` when no template exists.
- The report period is `(period.start, period.end]` in the project's time zone: for a range of more than one day the first day is left out, because its night belongs to the day before. A single-day range reports that day. A night counts for the day its sleep ends.
- The report covers sleep statistics, heart rate and respiratory rate for each night. It holds only what ends inside one of the resident's use periods of its device, as `GET /v1/slim/sleep/statistics` does: the sleep table's recordings, and the readings behind the heart and respiratory rate rows and charts. A reading with no end time is then left out. So a previous resident's night that ended before the device was handed over, but reached Ovok after it, is not reported. A resident with no device use history has everything reported.
- When a night has several sleep recordings left, the sleep table uses the one written last (latest `meta.lastUpdated`), the rule `GET /v1/slim/sleep/statistics` uses.
- Once a resident has any use period, the report also leaves out what a device with no period of theirs recorded, and what a device recorded before its earliest period. A device assigned before use periods were recorded gets its first one on its next edit in Ovok, starting at the Device's last write before that edit, so what it recorded before then is not reported. Each device whose readings in a report all drop is counted in the `sleep.device_use_device_dropped` metric and logged at most once a minute.
- Report times use the project's time zone. The `timeZone` field in the body is not used.
- The PDF is stored as a Binary, and a `DocumentReference` links it to the resident. Both are written in one transaction, so a failed store leaves neither. The response is that `DocumentReference`. Its `content[0].attachment.url` points to the Binary.
- When `mailTo` has addresses, the PDF is emailed to them as an attachment after the store succeeds. The email is sent in the language you set. With no addresses, nothing is emailed.
- If the email cannot be sent, the route returns `500`, but the report has already been stored.
- Every call creates a new report. Nothing is deduplicated.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/slim/export/pdf' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{
"code": "TRENDS_PDF_EXPORT",
"patientId": "c3499c27-2973-0a7f-807e-fb8676a92dcb",
"language": "en",
"period": { "start": "2026-09-21T00:00:00Z", "end": "2026-09-28T00:00:00Z" },
"mailTo": ["max.mustermann@example.com"]
}'
```
- Slim/Health
- [Get health status](/api/slim/health/get-health-status): Returns the health of the Slim API and the systems it depends on. Use it for uptime checks and status screens.
**Auth:** none.
## Behaviour
- `status` is `healthy` when both the FHIR store and Redis are up, `degraded` when one is down, and `unhealthy` when both are down. The route still answers `200` in every case, so read `status`, not the HTTP code.
- `externalSystems.fhirStore` and `externalSystems.redis` hold each system's own status and, when available, its version, memory and connection figures. `responseTimeMs` is the time the FHIR store took to answer its health check.
- `cache` holds hit, miss and error counts of the application cache since the process started.
- `ovok`, `system` and the FHIR store `serverMetrics` come from the monitoring system, from the last 5 minutes. They are left out when monitoring is not configured or does not answer.
- `uptimeSeconds` is the time since this process started. `version` is `null` when the deployment does not set one.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/health'
```
- Slim/Invite
- [Invite practitioner](/api/slim/invite/invite-practitioner): Invites a practitioner to your project, or to one of its direct child projects, and emails them a link to set their password.
**Auth:** Bearer token for a practitioner, project admin or System Owner session. The caller's access policy must grant `Practitioner:create` (a project admin who holds no access policy skips this check).
**Scope:** The caller's project (from the token), or `projectId` when given. `projectId` must be the caller's project or a direct child of it.
## Behaviour
- Practitioner invitations must be enabled on the target project. They are enabled unless the project turns them off.
- `accessPolicyId` must name a policy of your project, its parent or a direct child, at or below your own rank. It is checked before anything is written.
- An existing practitioner account with this `email` in the target project is refused: an invite never changes a member's role or admin flag (use `PUT /v1/slim/policy/{id}`). An existing practitioner account elsewhere, your own project included, is reused: only the membership and practitioner profile are new. A patient account of another project is not: it is that project's own, and the invitee gets a practitioner account of their own.
- Ovok creates the membership with the access policy and the practitioner profile, then emails an invitation with a password-setup link. The link points to the app the request came from, and sets a password for 7 days after it was sent. If the email fails, the account is undone and the request answers 400.
- `admin` in the body is ignored. The invitee becomes an admin member, and the project owner, only when the target project has no owner yet.
- The invitee shows as `Pending` in user lists until they set a password.
- Returns the new membership, the practitioner profile and `status: "Pending"`.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/slim/invite/practitioner' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{
"email": "max.mustermann@example.com",
"name": "Max",
"surname": "Mustermann",
"accessPolicyId": "c3499c27-2973-0a7f-807e-fb8676a92dcb"
}'
```
- Slim/Location
- [List location assignment candidates](/api/slim/location/list-location-assignment-candidates): Lists locations a resident can be placed in, flagged as occupied or vacant. Use it to fill the location picker when you assign a resident.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Location:search`, `Location:read`, `Device:read`, `Patient:read` and `Organization:read` (an admin or super admin who holds no access policy skips this check).
**Scope:** The caller's project (from the token). A non-admin token without a project is refused.
## Behaviour
- Read-only.
- Built from the same data as `GET /v1/slim/location`: **one row per device** at a location, and one row for a location with no device. `total` counts rows.
- `occupied` is `true` when the row's device has a resident assigned. Such rows carry `warningCode: "location_has_existing_resident"`. Otherwise `warningCode` is `null`.
- `assignable` is always `true`.
- Fields with no value are `null`.
- `search` and `organizationId` filter as on `GET /v1/slim/location`. `occupied` takes `occupied`, `vacant` or `all` (default `all`).
- Rows are sorted as on `GET /v1/slim/location`.
- Pagination is zero-based: `page` (default `0`) and `count` (default `50`, max `50`).
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/location/assignment-candidates?occupied=vacant&count=50' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
```json
{
"total": 1,
"page": 0,
"count": 50,
"resources": [
{
"locationId": "c3499c27-2973-0a7f-807e-fb8676a92dcb",
"locationName": "Room 201 Bed 1",
"organizationId": null,
"organizationName": null,
"deviceId": null,
"deviceName": null,
"patientId": null,
"patientName": null,
"occupied": false,
"assignable": true,
"warningCode": null
}
]
}
```
- [List locations](/api/slim/location/list-locations): Lists the locations (rooms and beds) in your project, with the device, resident and zone at each one. Use it for the location table in the Slim dashboard.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Location:search` and `Location:read` (an admin or super admin who holds no access policy skips this check).
**Scope:** The caller's project (from the token). Only locations, devices and residents in that project are returned.
## Behaviour
- Read-only.
- **One row per device.** A location with several devices appears once per device. A location with no device appears once, without device and resident fields. `total` counts rows, not locations.
- `patientId` and `patientName` are the resident assigned to that row's device. `organizationId` and `organizationName` are the location's zone.
- `search` matches, as a case-insensitive substring, the location name, the name of a device at the location, the name of a resident on such a device, or the zone name.
- `floor`, `room` and `bed` match the location's codes exactly. `organizationId` keeps locations in that zone. `deviceId` keeps only the row for that device.
- Rows are sorted by floor, room, bed, device name, then location name. The sort is case-insensitive and numeric-aware ("2" before "10"). Rows with a missing value sort after rows that have one.
- Pagination is zero-based: `page` (default `0`) and `count` (default `20`, max `100`).
- Unknown query parameters are rejected.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/location?search=Room%20201&page=0&count=20' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Create location](/api/slim/location/create-location): Creates a location (room or bed), optionally in a zone.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Location:create` and `Device:update` (an admin or super admin who holds no access policy skips this check).
**Scope:** The location is created in the caller's project (from the token). A super-admin caller can pass `?projectId=` to create it in that project instead. Any other caller who passes `projectId` gets a 403.
## Behaviour
- `name` must be unique. The check is an exact name match against the locations your token can search. With `?projectId`, the check runs in that project only.
- When any of `floor`, `room` or `bed` is set, the combination of zone, floor, room and bed must also be unique. The comparison ignores case and extra whitespace. A location with only a name has no such check.
- `floor`, `room` and `bed` are stored as type codes on the location. `organizationId` sets the zone. The zone must be in your project, one of its sub-projects, or its parent project.
- The create is guarded against a concurrent create of the same floor, room and bed. If another request creates it first, you get a 409 that names the existing location.
- No devices or residents are attached. Use `PUT /v1/slim/location/{id}` for that.
- A location-created entry is added to the change history. A failure to write that entry does not fail the request.
- The response is the created location, in the same shape as `GET /v1/slim/location/{id}`.
- If a location with the same zone, floor, room and bed exists, the request is refused with 409 and the body carries `existingLocationId` and `existingLocationName`.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/slim/location' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{
"name": "Room 201",
"floor": "2",
"room": "201",
"bed": "A",
"organizationId": "87ea5dfc-8b8e-384d-8489-79496e706390"
}'
```
- [List locations in a project (admin)](/api/slim/location/list-locations-in-a-project-admin): Lists the locations of a given project. The admin dashboard uses it to fill the location picker after you choose a project.
**Auth:** Bearer token. The caller must be a Medplum project admin. Super admins without the admin role, practitioners and System Owner callers are refused. The access-policy check (`Location:search`, `Location:read`) is skipped only for an admin who holds no access policy.
**Scope:** The project named by the required `projectId` query parameter. The search runs with Ovok's admin client, not the caller's token.
## Behaviour
- Read-only.
- Locations are sorted by name.
- Pagination is zero-based: `page` (default `0`) and `count` (default `50`, max `100`).
- `total` is an estimate: when a page is full it is one more than the rows seen so far, so a full page means more may follow.
- Unlike `GET /v1/slim/location`, rows carry no device or resident fields: one row per location.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/location/admin/list?projectId=c3499c27-2973-0a7f-807e-fb8676a92dcb&page=0&count=50' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Create location in a project (admin)](/api/slim/location/create-location-in-a-project-admin): Creates a location in a given project. The admin dashboard uses it during device assignment, when the right room does not exist yet.
**Auth:** Bearer token. The caller must be a Medplum project admin. Super admins without the admin role, practitioners and System Owner callers are refused. The access-policy check (`Location:create`) is skipped only for an admin who holds no access policy.
**Scope:** The project named by `projectId` in the body. The location is created with Ovok's admin client and stamped with that project.
## Behaviour
- `floor`, `room` and `bed` are stored as type codes. `organizationId` sets the zone (`managingOrganization`).
- The location is created with `status: active`.
- Unlike `POST /v1/slim/location`, this route does not check for a duplicate name or a duplicate floor, room and bed, and adds no change-history entry.
- The response is `{ id, name, projectId }` of the created location.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/slim/location/admin' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{
"projectId": "c3499c27-2973-0a7f-807e-fb8676a92dcb",
"name": "Room 18",
"floor": "2",
"room": "18",
"organizationId": "87ea5dfc-8b8e-384d-8489-79496e706390"
}'
```
- [Get location](/api/slim/location/get-location): Returns one location (room or bed) by id, with its zone, devices and resident.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Location:read` (an admin or super admin who holds no access policy skips this check).
**Scope:** The location is read with the caller's own token, so you get any location your Medplum access lets you read. There is no extra project check on this route.
## Behaviour
- Read-only.
- `floor`, `room` and `bed` come from the location's type codes.
- `organizationId` and `organizationName` are the location's zone (`Location.managingOrganization`).
- `deviceId` lists every device at the location. `deviceName` is the alphabetically first of their names.
- `patientId` and `patientName` are the resident on the first device at the location that has one.
- Fields with no value are omitted.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/location/c3499c27-2973-0a7f-807e-fb8676a92dcb' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
```json
{
"id": "c3499c27-2973-0a7f-807e-fb8676a92dcb",
"name": "Room 201",
"floor": "2",
"room": "201",
"bed": "A",
"organizationId": "87ea5dfc-8b8e-384d-8489-79496e706390",
"organizationName": "Ward A",
"deviceId": ["..."],
"deviceName": "...",
"patientId": "...",
"patientName": "Maxi"
}
```
- [Delete location](/api/slim/location/delete-location): Deletes a location that has no devices attached.
**Auth:** Bearer token. The caller must be a Medplum project admin or super admin. The access policy must grant `Location:delete` and `Device:update` (an admin or super admin who holds no access policy skips the access-policy check).
**Scope:** The location must belong to the caller's project (from the token).
## Behaviour
- Detach every device first (`PUT /v1/slim/location/{id}` with `"deviceId": []`). A location with a device attached is refused.
- The Location resource is deleted.
- The write is atomic and takes a short write lock. A concurrent write to the same location gets a 409.
- A location-deleted entry is added to the change history. A failure to write that entry does not fail the request.
- The response has no body.
## Example
```bash
curl -X DELETE 'https://api.sandbox.ovok.com/v1/slim/location/c3499c27-2973-0a7f-807e-fb8676a92dcb' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Update location](/api/slim/location/update-location): Updates a location: its name, floor, room, bed and zone, the devices at it, and the resident on those devices.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Location:update` and `Device:update` (an admin or super admin who holds no access policy skips this check).
**Scope:** The location must belong to the caller's project (from the token). Devices and residents you attach must be in your project or one of its sub-projects. The zone must be in your project, a sub-project, or the parent project.
## Behaviour
- `name` is required and must be unique among the locations your token can search, not counting this one.
- `floor`, `room`, `bed`: an omitted field keeps its current value. If the zone, floor, room or bed changes, the new combination must not match another location's.
- `organizationId`:
- omitted: the zone is unchanged.
- `null`: the zone is removed from the location and from every device at it.
- an id: the location and every device at it move to that zone. Residents on those devices move too.
- `deviceId` (one id or a list):
- omitted: the devices at the location are unchanged.
- a list: it becomes the full set of devices at the location. Devices not in the list are detached; new ones are attached. `null` or `[]` detaches all devices.
- Devices that hold a discharged resident cannot be attached.
- `patientId`:
- an id: that resident is assigned to the devices in `deviceId`, or, when `deviceId` is omitted or empty, to the single device at the location. The resident is first removed from any other device. A resident already on a target device is replaced. A discharged resident is refused.
- **omitted: every resident is removed from every device at the location and every device in `deviceId`.** To keep the current resident, send their `patientId`.
- A resident who loses a device has their active out-of-bed notification revoked in the same write, and the device's live telemetry state is cleared afterwards.
- Device use history is updated for every device whose resident changes.
- The write is atomic and takes a short write lock. A concurrent write to the same resources, or a device reassigned by someone else while this request ran, gets a 409.
- Every save adds a location-updated entry to the change history. Residents who move into a new zone, or lose a device, also get an entry in their own change history.
- The response is the location after the update, in the same shape as `GET /v1/slim/location/{id}`.
- If another location has the same zone, floor, room and bed, the request is refused with 409 and the body carries `existingLocationId` and `existingLocationName`.
## Example
```bash
curl -X PUT 'https://api.sandbox.ovok.com/v1/slim/location/c3499c27-2973-0a7f-807e-fb8676a92dcb' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{
"name": "Room 201",
"floor": "2",
"room": "201",
"bed": "A",
"organizationId": "87ea5dfc-8b8e-384d-8489-79496e706390",
"deviceId": ["c3499c27-2973-0a7f-807e-fb8676a92dcb"],
"patientId": "c3499c27-2973-0a7f-807e-fb8676a92dcb"
}'
```
- Slim/Notifications
- [List recent notifications](/api/slim/notifications/list-recent-notifications): Lists the newest active notifications stored as CommunicationRequests for your project, of every type (such as out-of-bed, device alarms and attention presses). Use it for a notification bell. For filtered or paged history, use the out-of-bed and alarm feeds.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `CommunicationRequest:read` and `CommunicationRequest:search` (an admin or super admin who holds no access policy skips this check).
**Scope:** The caller's project (from the token).
## Behaviour
- Returns at most `count` rows (1 to 50, default 20), newest first by event time.
- Rows come from a Redis copy of the active notifications when that copy is enabled and loaded, and from a FHIR store search otherwise. `warm` is `true` and `source` is `redis` for the first case; `warm` is `false` and `source` is `fhirStore` for the second. A cold read also starts loading the Redis copy in the background, so a later call can be warm.
- The Medplum search returns `active` notifications updated within the last year.
- `total` counts the active notifications the source knows about, capped at the list result cap (1,000 by default). From Medplum it is an estimate.
- `locationName` is the room's current name, read from the Location as on the notification lists, so a renamed room shows its new name on every row, old alerts and new. When rows come from Redis, that lookup is cached per caller for up to a minute and dropped when a room is renamed through this API. If the caller cannot read the Location, or the lookup takes more than 2 seconds, a row keeps the name it was raised with.
- Rows whose notification type cannot be resolved are left out.
- Medical notifications (heart rate, respiratory rate and other vitals) are decided by Signals and served by `GET /v1/slim/notifications/recent/medical`. To show one type only, use `GET /v1/slim/notifications/recent/oob` or `GET /v1/slim/notifications/recent/alarms`.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/notifications/recent?count=20' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [List recent medical notifications](/api/slim/notifications/list-recent-medical-notifications): Lists the newest open, unacknowledged medical notifications for your project. Use it for a medical notification bell. Medical episodes are decided by Signals; Ovok reads them from Signals and adds the resident context.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Patient:read`, `Device:read` and `Location:read` (an admin or super admin who holds no access policy skips this check).
**Scope:** The caller's project (from the token), applied on both sides: Signals filters on the resident's project stamp, and Ovok checks each resolved resident against your project.
## Behaviour
- The filter is fixed by the server: open, unacknowledged episodes, newest last notification first (`episode.lastFiringAt`, or `episode.openedAt` when that is null), at most `count` (1 to 50, default 20). Every row's `episode.status` is `OPEN`. An episode leaves the bell when it closes, whether or not anyone acknowledged it. Closed episodes stay in `GET /v1/slim/notifications/medical`, where they can still be acknowledged with `POST /v1/slim/notifications/medical/{episodeId}/ack`.
- An open episode is listed however many closed episodes fired after it.
- Rows have the same shape as `GET /v1/slim/notifications/medical`, including `episode.firedBand`.
- `nextCursor` is set when the page is full, so more open, unacknowledged episodes may exist. A bell can ignore it.
- A resident who cannot be resolved into your project is left out, so a page can hold fewer than `count` rows.
- A resident with no Signals project stamp matches no project and does not appear.
- When the project's Signals tenant has episodic alerts switched off, the list is empty.
- A 503 with `signals_unavailable` means Signals could not be reached, not that there are no notifications; retry after `retryAfterSec`.
- Out-of-bed is not here; read `GET /v1/slim/notifications/recent/oob`.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/notifications/recent/medical?count=20' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [List recent out-of-bed notifications](/api/slim/notifications/list-recent-out-of-bed-notifications): Lists the newest active out-of-bed notifications for your project. Use it for an out-of-bed notification bell.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `CommunicationRequest:read` and `CommunicationRequest:search` (an admin or super admin who holds no access policy skips this check).
**Scope:** The caller's project (from the token).
## Behaviour
- Out-of-bed alerts are episodes Signals decides (code `103214-3`); Ovok reads them from Signals and adds the resident context. Medical notifications are not here: read `GET /v1/slim/notifications/recent/medical` for those, so one event is never counted by both bells.
- Returns at most `count` rows (1 to 50, default 20): the newest unacknowledged episodes not closed by hand, by last firing, from the first 200 Signals holds for the last year.
- `deviceName` and `locationName` name the device the alert was raised on and its room's current name, also after the resident moved to another device or was unassigned.
- `total` counts every active row in those 200, capped at the list result cap (1,000 by default). `warm` is `true` and `source` is `signals`.
- An episode that has ended but has not been acknowledged stays active, and so stays in this bell, until someone acknowledges it with `PATCH /v1/slim/notifications/{id}/ack`.
- While Signals is unavailable, the bell answers the last page Signals gave it for your project, up to 10 minutes old and cut to `count`. An acknowledgement drops that page, so an acknowledged row never comes back. With no page kept, it answers 503 with `signals_unavailable`; retry after `retryAfterSec`.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/notifications/recent/oob?count=20' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [List recent alarm notifications](/api/slim/notifications/list-recent-alarm-notifications): Lists the newest active device alarms and attention presses for your project. Use it for an alarm bell. It has the same response shape as `GET /v1/slim/notifications/recent/oob`.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `CommunicationRequest:read` and `CommunicationRequest:search` (an admin or super admin who holds no access policy skips this check).
**Scope:** The caller's project (from the token).
## Behaviour
- Device alarms and attention presses are decided by the sending system. Ovok stores and forwards them.
- Returns at most `count` rows (1 to 50, default 20), newest first by event time.
- Always read from the FHIR store, limited to the `deviceAlarm` and `attention` types, so `warm` is `false` and `source` is `fhirStore`.
- `total` counts alarm rows only. It is an estimate, capped at the list result cap (1,000 by default).
- Acknowledge a row with `PATCH /v1/slim/notifications/{id}/ack`; it then leaves this bell.
- Out-of-bed is on `GET /v1/slim/notifications/recent/oob`, medical notifications on `GET /v1/slim/notifications/recent/medical`.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/notifications/recent/alarms?count=20' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [List out-of-bed notifications](/api/slim/notifications/list-out-of-bed-notifications): Lists your project's out-of-bed notifications, filtered and paged. Out-of-bed is evaluated and recorded by Ovok as CommunicationRequests, and this is its feed. Medical notifications are not here; read `GET /v1/slim/notifications/medical` for those.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `CommunicationRequest:read`, `CommunicationRequest:search` and `Observation:read` (an admin or super admin who holds no access policy skips this check).
**Scope:** The caller's project (from the token). The `project` query parameter is accepted but ignored.
## Behaviour
- The notification type is fixed to out-of-bed; there is no `type` parameter. Prefer this route over the deprecated `GET /v1/slim/notifications?type=outOfBed`.
- `status=active` lists the new notifications and `status=completed` the reviewed ones. Leave `status` out to list new and reviewed together; each row keeps its own `status`. An episode closed by hand without an acknowledgement is `revoked`, so it is in none of these lists; ask `status=revoked` for it. An episode that has ended but has not been acknowledged stays `active`, with an end-of-episode marker, so one resident can have more than one active row.
- One row per out-of-bed episode, not per reading: the resident's open episode is updated in place.
- Sort order: newest first by event time (`occurrenceDateTime`, then `authoredOn`, then id).
- `patient` takes a Patient id or `Patient/{id}`. `device` takes a Device id, `Device/{id}`, or the device's identifier value. A Device id that does not exist returns an empty page rather than an unfiltered one. `device` matches the device that raised each alert, and each row's device and room are that device's, also after the resident moved to another device or was unassigned.
- `startDate` and `endDate` are inclusive ISO 8601 UTC instants, used exactly as sent. They bind to the episode's last firing time (`occurrenceDateTime`), whatever the status. Either may be omitted. A missing `startDate` defaults to one year back for `status=active` and 30 days back otherwise. Without `status`, the new rows keep the one-year default and the reviewed rows the 30-day one, so the list holds exactly the rows of `status=active` and `status=completed` together.
- Paging: `page` from 0, `count` from 1 to 100 (default 25). `total` is an exact count for the filter, the same on every page, cached for 15 seconds. It counts matching CommunicationRequests, so it is an upper bound on the rows you can page through. It reads `0` if the count lookup fails; the rows are unaffected.
- The list pages through at most the first 1,000 results (server-configurable). `total` is clamped to that cap. When the cap or the per-request scan limit stops a page short, `truncated` says why: `{ reason: 'result-cap', limit }` or `{ reason: 'scan-budget', limit }`. `truncated` is `null` when the page is complete.
- Acknowledge a row with `PATCH /v1/slim/notifications/{id}/ack`.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/notifications/oob?status=active&page=0&count=25' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [List alarm notifications](/api/slim/notifications/list-alarm-notifications): Lists your project's device alarms and attention presses, filtered and paged. These are decided by the sending system; Ovok stores them as CommunicationRequests and forwards them.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `CommunicationRequest:read`, `CommunicationRequest:search` and `Observation:read` (an admin or super admin who holds no access policy skips this check).
**Scope:** The caller's project (from the token). The `project` query parameter is accepted but ignored.
## Behaviour
- One row per alarm episode. Each row's `alarm` carries the sender's own alarm id (`episodeId`), its severity, type and text, so the sender can match an acknowledgement to its own record.
- `type`: `deviceAlarm`, `attention`, or both comma separated. Both when omitted.
- `severity`: `RED`, `YELLOW`, `ATTENTION`, comma separated, as the sender coded the alarm. Every severity when omitted.
- `status` defaults to `active`; an acknowledged alarm is `completed`.
- `sort`: `newest` (default) or `oldest`, by event time (`occurrenceDateTime`, then `authoredOn`, then id).
- `patient`, `device`, `startDate`, `endDate`, `page`, `count`, `total` and `truncated` work as on `GET /v1/slim/notifications/oob`: `count` is 1 to 100 (default 25), dates are inclusive UTC instants, and the list pages through at most the first 1,000 results (server-configurable).
- Acknowledge a row with `PATCH /v1/slim/notifications/{id}/ack`.
- Out-of-bed is on `GET /v1/slim/notifications/oob`; vital-sign alerts, decided by Signals, are on `GET /v1/slim/notifications/medical`.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/notifications/alarms?type=deviceAlarm&severity=RED&sort=newest' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [List medical notifications](/api/slim/notifications/list-medical-notifications): Lists your project's medical notifications (heart rate, respiratory rate and other vital-sign alerts), newest last notification first, one page at a time. Medical episodes are decided by Signals; Ovok reads them from Signals, adds the resident context, and never re-derives a clinical value.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Patient:read`, `Device:read` and `Location:read` (an admin or super admin who holds no access policy skips this check).
**Scope:** The caller's project (from the token), applied on both sides: Signals filters on the resident's project stamp, and Ovok checks each resolved resident against your project.
## Behaviour
- Order: newest last notification first. That is `episode.lastFiringAt`, the newest reading outside the band, or `episode.openedAt` for an episode stored without one (before 2026-09-28). An open episode that fires again moves up, so while you read page 2 it can move to page 1. It is never listed twice, and the next read of page 1 shows it.
- One row per episode, not per reading: a run of readings outside the band is one episode, which closes on recovery or acknowledgement.
- Each row has three blocks. `episode` is the Signals episode with its aggregates. `carehub` is the resident context Signals does not hold: resident, room and bed, device, zone. `patientName` is the resident's Resident ID, also after a discharge, an unassign or a device move. The room, bed, device and zone are those of the device the episode was raised on, also after the resident moved to another device or was unassigned. They are `null` when that device is not known or is no longer in your project. `acknowledgement` is `null` until someone acknowledges the episode.
- Use `episode.id` with `GET /v1/slim/notifications/medical/{episodeId}` to get the full value history.
- `episode.firedBand` is the band the episode was judged under: the band Signals recorded when it fired, or else the band rebuilt from Signals' configuration history at the episode's trigger version. It is not the resident's current band, so editing a threshold does not change past rows. `min` and `max` are each nullable (a band can be one-sided). `firedBand` is `null` when the band cannot be established; that never fails the read.
- Filters: `status` (`OPEN` or `CLOSED`, both when omitted), `since` (episodes whose last notification, as in the order above, is at or after this ISO 8601 instant), `acknowledged` (`true`, `1` or `yes` for acknowledged only; any other value for unacknowledged only; both when omitted).
- `patient` narrows the feed to one resident, by Medplum `Patient.id` (the `carehub.patientId` a row returns). It does not change the project scope. A resident who cannot be resolved, or who is in another project, answers 404 rather than an unfiltered page.
- `codes` narrows the feed to these LOINC codes, comma separated, at most 32 (for example `8867-4,9279-1`). Perfusion index is `PI`. Signals applies the filter. Out-of-bed is never in this feed.
- Paging: `limit` is 1 to 200 (default 50). Pass the previous page's `nextCursor` as `cursor`. Page until `nextCursor` is `null`, not until a short page arrives: rows whose resident cannot be resolved into your project are dropped after Signals counted them.
- A resident with no Signals project stamp matches no project and does not appear.
- When the project's Signals tenant has episodic alerts switched off, the page is empty.
- Out-of-bed is served by `GET /v1/slim/notifications/oob`.
- A 503 with `signals_unavailable` means Signals could not be reached; retry after `retryAfterSec`.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/notifications/medical?status=OPEN&codes=8867-4,9279-1&limit=50' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Get medical notification](/api/slim/notifications/get-medical-notification): Returns one medical notification with its full value history, addressed by the `episode.id` a feed row carries. The episode is read from Signals, which decides medical alerts.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Patient:read`, `Device:read` and `Location:read` (an admin or super admin who holds no access policy skips this check).
**Scope:** The caller's project (from the token), applied on both sides: Signals matches it against the resident's project stamp, and Ovok checks the resolved resident against your project.
## Behaviour
- `episode` is the Signals episode as Signals returned it: status, open and close times, close reason, aggregates, and every reading recorded while it ran (`values`). Check `episode.truncated`: a very long episode returns a capped series and says so.
- `carehub` adds the resident context: resident, room and bed, device and zone. `patientName` is the resident's Resident ID, also after a discharge, an unassign or a device move. The room, bed, device and zone are those of the device the episode was raised on, also after the resident moved to another device or was unassigned. They are `null` when that device is not known or is no longer in your project.
- `acknowledgement` is `null` until someone acknowledges the episode.
- `episode.firedBand` is the band the episode was judged under, as described on `GET /v1/slim/notifications/medical`. It is `null` when it cannot be established.
- An episode in another project, or an out-of-bed episode (code `103214-3`), answers 404, the same as one that does not exist. A 404 is never caused by Signals being down.
- A 503 with `signals_unavailable` means Signals could not be reached; retry after `retryAfterSec`.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/notifications/medical/c3499c27-2973-0a7f-807e-fb8676a92dcb' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Acknowledge medical notification](/api/slim/notifications/acknowledge-medical-notification): Acknowledges one medical notification, so it leaves the medical bell. The acknowledgement is written to Signals, which owns medical episodes.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Patient:read` (an admin or super admin who holds no access policy skips this check).
**Scope:** The caller's project (from the token). Ovok reads the episode with your project and checks its resident against your project before it writes anything.
## Behaviour
- No CommunicationRequest is created or changed. Signals closes the episode with reason `ACKNOWLEDGED`. A resident still outside the band opens a new episode on their next reading.
- Repeating the call is safe.
- Answers 200 with the acknowledged row, in the feed's row shape, already carrying the new `acknowledgement` and closed status. Replace the list entry with it; no refetch is needed.
- `acknowledgement.acknowledgedBy` is Ovok's client id at Signals, not a person; do not show it as a name. `acknowledgement.acknowledgedActor` is the caller's profile reference and display name, or `null` when the session has no profile.
- Ovok clears its cached index of the project's open episodes, so other reads see the episode as closed straight away rather than after a cache expiry.
- An episode in another project, an out-of-bed episode (code `103214-3`) or one that does not exist answers the same 404, and nothing is acknowledged. Acknowledge an out-of-bed alert with `PATCH /v1/slim/notifications/{id}/ack`.
- A 409 means the project's Signals tenant has episodic alerts switched off.
- While Signals does not answer, Ovok keeps trying for up to 12 seconds before it answers. A 503 (`signals_unavailable`) means Ovok could not confirm the acknowledgement; repeat the call after `retryAfterSec` (also sent as `Retry-After`).
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/slim/notifications/medical/c3499c27-2973-0a7f-807e-fb8676a92dcb/ack' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Leave a note on a notification](/api/slim/notifications/leave-a-note-on-a-notification): Leaves a note on a notification. A note is a practitioner's comment on a notification, stored as a FHIR `Communication` whose `basedOn` points at the alert. The alert itself is never changed.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Patient:read` (an admin or super admin who holds no access policy skips this check). Only a practitioner can write notes.
**Scope:** The caller's project (from the token). An alert or note from another project, of another kind, or that does not exist answers the same 404.
## Behaviour
- `kind` names the feed the notification comes from: `oob` or `alarm` with the row's `commId` as `id`, `medical` with the row's `episode.id`, or `alert` with a raw Signals alert's `id`.
- A note on a raw alert is also listed and counted on that alert's episode. Which episode is decided once, when the note is written.
- `text` is trimmed and must be 1 to 2000 characters.
- The author is the calling practitioner, never a body field.
- Notes can be left on acknowledged notifications too.
- `medical` and `alert` notes are read from Signals first, so they answer 503 `signals_unavailable` while Signals is down. Retry once Signals is back.
- Answers 201 with the note as stored.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/slim/notifications/notes' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{ "kind": "oob", "id": "c3499c27-2973-0a7f-807e-fb8676a92dcb", "text": "Resident was in the bathroom." }'
```
- [An out-of-bed notification's notes](/api/slim/notifications/an-out-of-bed-notification-s-notes): Lists the notes on an out-of-bed notification. A note is a practitioner's comment on a notification, stored as a FHIR `Communication` whose `basedOn` points at the alert. The alert itself is never changed.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `CommunicationRequest:read` and `CommunicationRequest:search` (an admin or super admin who holds no access policy skips this check).
**Scope:** The caller's project (from the token). An alert or note from another project, of another kind, or that does not exist answers the same 404.
## Behaviour
- `id` is the notification row's `commId`.
- Newest first. Deleted notes are not listed.
- Read-only.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/notifications/oob/c3499c27-2973-0a7f-807e-fb8676a92dcb/notes' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [An alarm notification's notes](/api/slim/notifications/an-alarm-notification-s-notes): Lists the notes on an alarm notification. A note is a practitioner's comment on a notification, stored as a FHIR `Communication` whose `basedOn` points at the alert. The alert itself is never changed.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `CommunicationRequest:read` and `CommunicationRequest:search` (an admin or super admin who holds no access policy skips this check).
**Scope:** The caller's project (from the token). An alert or note from another project, of another kind, or that does not exist answers the same 404.
## Behaviour
- `id` is the notification row's `commId`.
- Newest first. Deleted notes are not listed.
- Read-only.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/notifications/alarms/c3499c27-2973-0a7f-807e-fb8676a92dcb/notes' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [A medical notification's notes](/api/slim/notifications/a-medical-notification-s-notes): Lists the notes on a medical notification. A note is a practitioner's comment on a notification, stored as a FHIR `Communication` whose `basedOn` points at the alert. The alert itself is never changed.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Patient:read`, `Device:read` and `Location:read` (an admin or super admin who holds no access policy skips this check).
**Scope:** The caller's project (from the token). An alert or note from another project, of another kind, or that does not exist answers the same 404.
## Behaviour
- `episodeId` is the feed row's `episode.id`.
- Newest first. Deleted notes are not listed.
- The episode is read from Signals first, as the detail and the acknowledgement read it, so this answers 503 `signals_unavailable` while Signals is down.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/notifications/medical/c3499c27-2973-0a7f-807e-fb8676a92dcb/notes' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [A raw Signals alert's notes](/api/slim/notifications/a-raw-signals-alert-s-notes): Lists the notes on a raw Signals alert. A note is a practitioner's comment on a notification, stored as a FHIR `Communication` whose `basedOn` points at the alert. The alert itself is never changed.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Patient:read` (an admin or super admin who holds no access policy skips this check).
**Scope:** The alert is read from Signals first: 404 when it isn't in the caller's project or the caller can't read its resident.
## Behaviour
- Newest first. Deleted notes are not listed.
- Answers 503 `signals_unavailable` while Signals is down. Retry once Signals is back.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/notifications/raw-alerts/c3499c27-2973-0a7f-807e-fb8676a92dcb/notes' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Edit a note's text](/api/slim/notifications/edit-a-note-s-text): Changes a note's text. A note is a practitioner's comment on a notification, stored as a FHIR `Communication` whose `basedOn` points at the alert. The alert itself is never changed.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Patient:read` (an admin or super admin who holds no access policy skips this check). Only the note's author can change it.
**Scope:** The caller's project (from the token). An alert or note from another project, of another kind, or that does not exist answers the same 404.
## Behaviour
- Same effect as `PUT`: a note's text is the only field a caller can change.
- `text` is trimmed and must be 1 to 2000 characters.
- If the note changed while your edit was in flight, the route answers 409. Reload the note and try again.
## Example
```bash
curl -X PATCH 'https://api.sandbox.ovok.com/v1/slim/notifications/notes/c3499c27-2973-0a7f-807e-fb8676a92dcb' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{ "text": "Resident was in the bathroom; back in bed 02:14." }'
```
- [Delete a note](/api/slim/notifications/delete-a-note): Deletes a note. A note is a practitioner's comment on a notification, stored as a FHIR `Communication` whose `basedOn` points at the alert. The alert itself is never changed.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Patient:read` (an admin or super admin who holds no access policy skips this check). Only the note's author can delete it.
**Scope:** The caller's project (from the token). An alert or note from another project, of another kind, or that does not exist answers the same 404.
## Behaviour
- The note is marked `entered-in-error`, not erased, so the clinical record and its history survive.
- A deleted note leaves every list and every `noteCount`.
- Answers 204 with no body.
## Example
```bash
curl -X DELETE 'https://api.sandbox.ovok.com/v1/slim/notifications/notes/c3499c27-2973-0a7f-807e-fb8676a92dcb' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Replace a note's text](/api/slim/notifications/replace-a-note-s-text): Changes a note's text. A note is a practitioner's comment on a notification, stored as a FHIR `Communication` whose `basedOn` points at the alert. The alert itself is never changed.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Patient:read` (an admin or super admin who holds no access policy skips this check). Only the note's author can change it.
**Scope:** The caller's project (from the token). An alert or note from another project, of another kind, or that does not exist answers the same 404.
## Behaviour
- Same effect as `PATCH`: a note's text is the only field a caller can change.
- `text` is trimmed and must be 1 to 2000 characters.
- If the note changed while your edit was in flight, the route answers 409. Reload the note and try again.
## Example
```bash
curl -X PUT 'https://api.sandbox.ovok.com/v1/slim/notifications/notes/c3499c27-2973-0a7f-807e-fb8676a92dcb' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{ "text": "Resident was in the bathroom; back in bed 02:14." }'
```
- [Acknowledge notification](/api/slim/notifications/acknowledge-notification): Acknowledges one notification stored as a CommunicationRequest (out-of-bed, device alarm, attention press) by setting its status to `completed`. For a medical notification, use `POST /v1/slim/notifications/medical/{episodeId}/ack` instead.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Patient:read` (an admin or super admin who holds no access policy skips this check). No `CommunicationRequest:update` grant is needed.
**Scope:** The CommunicationRequest is read with your own credentials and must belong to your project (from the token). Sub-projects are not included.
## Behaviour
- After those checks pass, Ovok writes the status change with its own credentials.
- Acknowledging an already completed notification succeeds and changes nothing.
- If the notification is updated at the same moment (for example by a new reading on the same episode), Ovok re-reads it and retries, keeping the newer content, up to three times.
- The notification leaves the active lists and bells. The device's alert state in the live views is cleared.
- Acknowledging a running out-of-bed episode stops that episode from raising the alert again. A new bed exit raises a new alert.
- Answers 200 with the updated row, in the same shape as the list rows.
- A 409 means the notification kept changing and all three attempts lost the race. Re-read it and call again.
## Example
```bash
curl -X PATCH 'https://api.sandbox.ovok.com/v1/slim/notifications/c3499c27-2973-0a7f-807e-fb8676a92dcb/ack' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- Slim/Organization
- [Get organization](/api/slim/organization/get-organization): Returns one organization (a zone in the Slim dashboard) by id. Use it to show or edit a zone.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Organization:read` (an admin or super admin who holds no access policy skips this check).
**Scope:** The organization is read with the caller's own token, so you get any organization your Medplum access lets you read. There is no extra project check on this route.
## Behaviour
- Read-only.
- `description` is the organization's narrative text with the XHTML `` wrapper removed. It is absent when the organization has no narrative.
- `parentOrganizationId` is the id from `Organization.partOf`. It is absent for a top-level zone.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/organization/c3499c27-2973-0a7f-807e-fb8676a92dcb' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
```json
{
"id": "c3499c27-2973-0a7f-807e-fb8676a92dcb",
"name": "Ward A",
"parentOrganizationId": "87ea5dfc-8b8e-384d-8489-79496e706390",
"description": "Ground floor"
}
```
- [Delete organization](/api/slim/organization/delete-organization): Deletes an organization (zone) and moves its residents, rooms and devices out of it.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Organization:delete` and `Device:update` (an admin or super admin who holds no access policy skips this check).
**Scope:** The organization must belong to the caller's project (from the token).
## Behaviour
- The Organization resource is deleted.
- Every patient, location and device that references the zone is re-pointed:
- If the zone has a parent zone in your project, they move to the parent zone.
- Otherwise their zone reference is removed.
- Device use history is updated for any device whose assignment changes.
- The writes run in one bundle. Because a large zone can exceed the size limit of an atomic write, this route does not take the atomic write lock. A very large zone is written as a checked batch, so a partial failure is reported as an error rather than rolled back.
- A zone-deleted entry is added to the change history. A failure to write that entry does not fail the request.
- The response has no body.
## Example
```bash
curl -X DELETE 'https://api.sandbox.ovok.com/v1/slim/organization/c3499c27-2973-0a7f-807e-fb8676a92dcb' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Update organization](/api/slim/organization/update-organization): Updates an organization's (zone's) name, description and parent zone.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Organization:update` (an admin or super admin who holds no access policy skips this check).
**Scope:** The organization must belong to the caller's project (from the token). Organizations in a parent project or a sub-project are refused.
## Behaviour
- `name` is required and must be unique among the organizations your token can search, not counting this one.
- `parentOrganizationId` sets the parent zone. It must name an organization in your own project. **Omitting it removes the current parent**, which makes the zone top-level.
- `description`, when non-empty, replaces the narrative text. Omitting it, or sending `null` or an empty string, leaves the existing description unchanged.
- The write is atomic and takes a short write lock. A concurrent write to the same resources gets a 409.
- A zone-updated entry is added to the change history. A failure to write that entry does not fail the request.
- The response is the organization after the update, in the same shape as `GET /v1/slim/organization/{id}`.
## Example
```bash
curl -X PUT 'https://api.sandbox.ovok.com/v1/slim/organization/c3499c27-2973-0a7f-807e-fb8676a92dcb' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{
"name": "Ward A",
"parentOrganizationId": "87ea5dfc-8b8e-384d-8489-79496e706390",
"description": "Ground floor"
}'
```
- [Create organization](/api/slim/organization/create-organization): Creates an organization (a zone in the Slim dashboard), optionally as a child of another zone.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Organization:create` (an admin or super admin who holds no access policy skips this check).
**Scope:** The organization is created in the caller's project (from the token). A super-admin caller can pass `?projectId=` to create it in that project instead. Any other caller who passes `projectId` gets a 403.
## Behaviour
- `name` must be unique. The check is an exact name match against the organizations your token can search. With `?projectId`, the check runs in that project only.
- `parentOrganizationId`, when set, must name an organization in your own project. A sub-project's or parent project's organization is refused. The new zone's `partOf` points at it.
- `description`, when non-empty, is stored as the organization's narrative text. `null` or an empty string stores nothing.
- The write is atomic and takes a short write lock. A concurrent write to the same resources gets a 409.
- A zone-created entry is added to the change history. A failure to write that entry does not fail the request.
- The response is the created organization, in the same shape as `GET /v1/slim/organization/{id}`.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/slim/organization' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{
"name": "Ward A",
"parentOrganizationId": "87ea5dfc-8b8e-384d-8489-79496e706390",
"description": "Ground floor"
}'
```
- Slim/Patient
- [List patients](/api/slim/patient/list-patients): Lists the residents (patients) of your project with their zone, room, device and settings tags. Use it for the residents table in the Slim dashboard.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Patient:search`, `Patient:read`, `Device:read`, `Location:read` and `Organization:read` (an admin or super admin who holds no access policy skips this check).
**Scope:** The caller's project (from the token). A token without a project is refused.
## Behaviour
- Read-only.
- One row per resident. When a resident is on several devices, the row shows one of them, so a resident is never listed twice.
- `residentState` defaults to `active`, so discharged residents are hidden unless you ask for them. `discharged-recent` means discharged within the last 30 days; `discharged` means longer ago. `all` turns the filter off.
- `includeDischargedRecent=true` widens `residentState=active` or `residentState=discharged` to also include `discharged-recent` residents.
- `organizationId` keeps residents in that zone. `hasDevice=true` keeps residents with a device; `hasDevice=false` keeps residents without one.
- `medicalSettings` and `bedActivitySettings` keep residents with that settings tag. These tags are `null` when Ovok could not read the project's threshold settings; such rows match no settings filter.
- `search` is a case-insensitive substring match on the resident's name, nickname, room name and zone name.
- Sort with `sortBy` (default `patientName`) and `orderBy` (`ASC` or `DESC`, any case, default `ASC`).
- Pagination is zero-based: `page` (default `0`) and `count` (default `20`, max `100`). `total` counts all matching rows.
- Fields with no value are `null`.
- Results are cached for up to 60 seconds per user. A change to a resident, device, room or zone made through Ovok clears the cache.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/patient?residentState=active&sortBy=patientNickname&orderBy=asc&page=0&count=20' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Create patient](/api/slim/patient/create-patient): Creates a resident (patient), optionally placed on devices, in a room and in a zone, and registers them with Signals.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Patient:create`, plus `Device:update` when `deviceId` is set (an admin or super admin who holds no access policy skips this check).
**Scope:** The resident is created in the caller's project (from the token). A super-admin caller can pass `?projectId=` to create it in that project instead. Any other caller who passes `projectId` gets a 403. Devices, rooms and zones you reference must be in your project, its parent project, or one of its sub-projects.
## Behaviour
- `name` must contain an entry with `use: "nickname"` and a non-blank first `given` value. This is the Resident ID.
- The Resident ID must be unique in the project, compared as in `POST /v1/slim/patient/check-name-exists`.
- `admissionDate` defaults to now. It cannot be later than the start of the current day.
- The resident is created as `active`, and an `EpisodeOfCare` is opened for the admission.
- Placement:
- `deviceId` (one id or a list): the resident is assigned to those devices. A device that already has a resident is reassigned to the new one, and the resident who had it loses it, with its room, as on `PUT /v1/slim/patient/{id}`.
- `locationId` with `deviceId`: those devices are also moved to the room.
- `locationId` without `deviceId`: the resident is assigned to the room's device. The room must have exactly one device.
- `organizationId` without `locationId` or `deviceId` is refused with 422: a resident's zone is read from their device, so a zone alone would not show.
- `organizationId`: the resident is placed in the zone. When `deviceId` is set, those devices and their rooms move to the zone too.
- Without `organizationId`, the resident's zone is their device's: the room's zone, else the device's own. Devices in more than one zone are refused with 422.
- The resident is registered with Signals in the same request. If that fails, everything this request wrote is rolled back and you get a 503 with `error: "signals_mint_failed"`. You can retry.
- The write is atomic and locks the Resident ID and the devices, so two concurrent creates of the same Resident ID leave one resident.
- Change-history entries are added for each assignment, including the zone the resident gets from their device, and for a resident who lost a device to the new one.
- The response is the created resident, in the same shape as `GET /v1/slim/patient/{id}`.
- If the resident cannot be registered with Signals, the create is rolled back and the request fails with 503. Retry it.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/slim/patient' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{
"name": [
{ "use": "nickname", "given": ["Maxi"] },
{ "use": "official", "given": ["Max"], "family": "Mustermann" }
],
"gender": "male",
"birthDate": "1940-05-01",
"deviceId": "c3499c27-2973-0a7f-807e-fb8676a92dcb",
"locationId": "c3499c27-2973-0a7f-807e-fb8676a92dcb",
"organizationId": "87ea5dfc-8b8e-384d-8489-79496e706390"
}'
```
- [Get patient current vitals](/api/slim/patient/get-patient-current-vitals): Returns the latest live telemetry for a resident: one entry per device assigned to them, with the device, resident and zone.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `search` and `read` on `Patient`, `Device`, `Location`, `Observation` and `Organization` (an admin or super admin who holds no access policy skips this check).
**Scope:** The caller's project (from the token). A resident outside that project returns `total: 0` and no entries.
## Behaviour
- Read-only.
- Returns one entry per device assigned to the resident, up to 100. A resident with no device returns `total: 0`.
- The response is cached for up to 60 seconds, so `telemetry` can lag the latest reading by up to a minute.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/patient/c3499c27-2973-0a7f-807e-fb8676a92dcb/current-vitals' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Get patient $everything](/api/slim/patient/get-patient-everything): Returns a FHIR `Bundle` with the resident and every resource that references them, using the FHIR `Patient/$everything` operation. Use it to export or inspect a resident's full record.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Patient:read` (an admin or super admin who holds no access policy skips this check).
**Scope:** The resident must belong to the caller's project (from the token) or one of its sub-projects. A resident in the parent project is refused.
## Behaviour
- Read-only.
- The bundle is produced by Medplum and read with the caller's own token, so it contains only resources that token can read.
- The response is a raw FHIR `Bundle`, not a Slim DTO.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/patient/c3499c27-2973-0a7f-807e-fb8676a92dcb/$everything' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Get patient](/api/slim/patient/get-patient): Returns one resident (patient) by id, with their names, resident state and current placement. The response is the shape `PUT /v1/slim/patient/{id}` accepts back.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Patient:read` (an admin or super admin who holds no access policy skips this check).
**Scope:** The resident is read with the caller's own token, so you get any patient your Medplum access lets you read. There is no extra project check on this route.
## Behaviour
- Read-only.
- `deviceId` is one device id, or `null` when the resident has no device. If the resident is on several devices, it is the most recently updated one. `locationId`, `floor`, `room`, `bed` and `organizationId` describe that same device's placement.
- `residentState` is `active`, `discharged-recent` (discharged within the last 30 days), `discharged` or `archived`.
- `admissionDate` and `dischargeDate` are returned as stored.
- A `name` entry without a `use` is returned as `official`.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/patient/c3499c27-2973-0a7f-807e-fb8676a92dcb' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Delete patient](/api/slim/patient/delete-patient): Deletes a resident (patient) who has no device.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Patient:delete` and `Device:update` (an admin or super admin who holds no access policy skips this check).
**Scope:** The resident must belong to the caller's project (from the token).
## Behaviour
- A resident who still has a device is refused with 409 `Resident still has device . Unassign it first.`, and nothing is changed. Take the device off the resident first (`PUT /v1/slim/patient/{id}` without `locationId`), then delete.
- Only the Patient resource is deleted. This route does not delete the resident's other records.
- Archived residents cannot be deleted.
- A change-history entry records the delete.
- The response has no body.
## Example
```bash
curl -X DELETE 'https://api.sandbox.ovok.com/v1/slim/patient/c3499c27-2973-0a7f-807e-fb8676a92dcb' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Update patient](/api/slim/patient/update-patient): Updates a resident (patient): names, gender, birth date, placement on devices, room and zone, and resident state (discharge or reactivation).
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Patient:update`, plus `Device:update` when the request changes which devices the resident is on (an admin or super admin who holds no access policy skips this check).
**Scope:** The resident must belong to the caller's project (from the token). Devices, rooms and zones you reference must be in your project, its parent project, or one of its sub-projects.
## Behaviour
The body is a full replacement of the editable fields, designed so that the result of `GET /v1/slim/patient/{id}` can be sent back unchanged. Fields the update does not declare, such as `id`, `floor` or `admissionDate`, are ignored.
- `name` replaces all of the resident's names, each trimmed of leading and trailing spaces. It must contain a `nickname` entry with a non-blank first `given` value. If the nickname changes, it must be unique in the project.
- `gender` and `birthDate` are replaced. Omitting them, or sending `""`, clears them.
- **Placement depends on `locationId`:**
- **`locationId` omitted or `""`: the resident is removed from all of their devices**, and `deviceId` and `organizationId` are ignored. Send the current `locationId` to keep the placement.
- `locationId` with `deviceId`: the resident is placed on those devices, and the devices move to the room. The resident leaves any other device. A resident already on one of those devices is replaced.
- `locationId` without `deviceId`: the resident is placed on the room's device. The room must have exactly one device.
- `locationId` with `deviceId` `null`, `[]` or `""` is refused with 422 and changes nothing: a room is not a placement without a device.
- A resident in a room is in the room's zone. An `organizationId` that differs from the zone of the room the resident stays in answers 422 with `path: ["organizationId"]` and changes nothing: move the room to that zone with `PUT /v1/slim/location/{id}`, or the resident to a room in it. A resident moving to another room takes that room's zone. When the room has no zone, `organizationId` re-zones the resident's devices.
- A save that puts the resident on devices in more than one zone, when they were not in exactly those zones before, is refused with 422.
- A discharged resident (`discharged` or `discharged-recent`) placed on a device they are not on is refused with 422 and nothing changes, with or without `deviceId`. Send `residentState: "active"` in the same save to reactivate and place them.
- `residentState`:
- `discharged` on an active resident: the resident is tagged discharged, `dischargeDate` is stored (default: now), the `EpisodeOfCare` is finished, and the resident is removed from all devices. `locationId` must be omitted or `""`.
- `discharged` on a resident already discharged: only a different `dischargeDate` has an effect; it corrects the stored date.
- `active` on a discharged resident: the resident is reactivated, the latest `EpisodeOfCare` is reopened and `dischargeDate` is removed.
- Other values, or the current state, change nothing.
- Any resident who loses a device in this request has their active out-of-bed notification revoked in the same write, and the device's live telemetry state is cleared afterwards.
- Archived residents cannot be updated.
- The write is atomic and takes a short write lock on the resident, the devices and, on a rename, the new Resident ID.
- Change-history entries are added for each changed field and assignment. A resident replaced on a device gets one naming the device, and the room and zone they leave with it.
- The response is the resident after the update, in the same shape as `GET /v1/slim/patient/{id}`.
## Example
```bash
curl -X PUT 'https://api.sandbox.ovok.com/v1/slim/patient/c3499c27-2973-0a7f-807e-fb8676a92dcb' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{
"name": [{ "use": "nickname", "given": ["Maxi"] }],
"gender": "male",
"birthDate": "1940-05-01",
"deviceId": "c3499c27-2973-0a7f-807e-fb8676a92dcb",
"locationId": "c3499c27-2973-0a7f-807e-fb8676a92dcb",
"organizationId": "87ea5dfc-8b8e-384d-8489-79496e706390",
"residentState": "active"
}'
```
- [Check patient name exists](/api/slim/patient/check-patient-name-exists): Checks whether a Resident ID (the resident's nickname) is already used in your project. Use it to validate the Resident ID field before you create or rename a resident.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Patient:read` (an admin or super admin who holds no access policy skips this check).
**Scope:** Residents in the caller's project (from the token).
## Behaviour
- Read-only.
- Only the `name` entry with `use: "nickname"` is compared. Official and maiden names are ignored. Without a nickname entry the answer is `exists: false`.
- A match needs the same `given` tokens in the same order and the same `family` (or no `family` on both sides). Tokens are trimmed and compared case-insensitively.
- When you edit an existing resident, pass their id as `excludePatientId` so their own record does not count as a match.
- The create and update routes run the same check, so a `true` here means those requests would be refused with a 422.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/slim/patient/check-name-exists' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{
"name": [{ "use": "nickname", "given": ["Maxi"] }],
"excludePatientId": "c3499c27-2973-0a7f-807e-fb8676a92dcb"
}'
```
```json
{ "exists": false }
```
- Slim/Policy
- [List access policies](/api/slim/policy/list-access-policies): Lists the access policies (roles) of a project, each with the practitioners who hold it. Use it to render a roles table.
**Auth:** Bearer token for a practitioner, project admin or System Owner session. The caller's access policy must grant `Practitioner:create`, the right to invite users (a project admin who holds no access policy skips this check).
**Scope:** The caller's project (from the token), or `projectId` when given. `projectId` must be the caller's project or a direct child of it.
## Behaviour
- Each row is `{ policy, members }`. `members` lists only `Practitioner` members, as `{ reference, display }`.
- `profile` keeps only the policies held by that member, given as a reference such as `Practitioner/`.
- `sortBy` is `_id` (default), `_lastUpdated` or `name`; `orderBy` is `ASC` (default) or `DESC`. Rows with an empty sort value go last in `ASC` and first in `DESC`.
- Paging is in memory: `page` (default `0`) and `count` (default `20`, 1 to 100). `total` counts the rows after filtering, before paging.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/policy?sortBy=name&orderBy=ASC&page=0&count=20' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Create access policy](/api/slim/policy/create-access-policy): Creates an access policy (role) in your project and assigns it to the listed practitioners. Use it to define a new role.
**Auth:** Bearer token for a practitioner, project admin or System Owner session. The caller's access policy must grant `AccessPolicy:create` and `ProjectMembership:update` (a project admin who holds no access policy skips this check).
**Scope:** The policy is created in the caller's project (from the token).
## Behaviour
- You cannot create a policy above your own rank. Ranks come from the policy name or its `basedOn` display: `System Owner` is the highest, then `Admin`, then every other policy. Project admins rank as System Owner.
- Ovok adds interactions the apps rely on:
- `Consent`: `read`, `search`, `create`, `update`, always;
- `DeviceUseStatement` and `EpisodeOfCare`: `read`, `search`, `create`, `update`, when the policy may create or update `Patient`;
- `Provenance` and `AuditEvent`: `read`, `search`, when the policy is named `Admin` or `System Owner`.
- `members` are profile references such as `Practitioner/`. Each listed member's membership in your project now points to the new policy, replacing the policy it held before. A member with no membership in your project is skipped.
- You cannot list a member whose current role ranks above yours. A member ranks as every policy their membership holds now (`accessPolicy` and any `access` entries), each ranked by its name. A project admin (`admin: true`) ranks as System Owner.
- The policy and the membership changes commit together. Past 49 membership changes they are written in two steps.
- The policy is then copied by name into each direct child project. A failed copy does not fail the request.
- Returns the created policy with its members, as in `GET /v1/slim/policy/{id}`.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/slim/policy' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{
"policy": {
"resourceType": "AccessPolicy",
"name": "Night shift",
"resource": [{ "resourceType": "Patient", "interaction": ["read", "search"] }]
},
"members": [{ "reference": "Practitioner/c3499c27-2973-0a7f-807e-fb8676a92dcb", "display": "Max Mustermann" }]
}'
```
- [Get access policy](/api/slim/policy/get-access-policy): Returns one access policy (role) with the practitioners who hold it.
**Auth:** Bearer token for a practitioner, project admin or System Owner session. The caller's access policy must grant `Practitioner:create`, the right to invite users (a project admin who holds no access policy skips this check).
**Scope:** The policy must belong to the caller's project (from the token), its parent project or one of its direct child projects.
## Behaviour
- Returns `{ policy, members }`. `policy` is the full `AccessPolicy`. `members` lists the `Practitioner` members whose membership points to it, as `{ reference, display }`.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/policy/c3499c27-2973-0a7f-807e-fb8676a92dcb' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Delete access policy](/api/slim/policy/delete-access-policy): Deletes an access policy (role) of your project and detaches it from everyone who holds it.
**Auth:** Bearer token for a practitioner, project admin or System Owner session. The caller's access policy must grant `AccessPolicy:delete` and `ProjectMembership:update` (a project admin who holds no access policy skips this check).
**Scope:** The policy must belong to the caller's project (from the token).
## Behaviour
- A policy is not deleted while a holder would be left with no role (no `access` entry, not a project admin): the request answers 409 until they are moved to another role, because Medplum reads a membership with no policy as full access. Other holders are detached.
- You cannot delete a policy you hold yourself.
- You cannot delete a policy held by anyone whose role ranks above yours (a project admin ranks as System Owner), since that takes their role away.
- Detaching the members and deleting the policy commit together. Past 49 membership changes they are written in two steps.
- The policy's copies of the same name in direct child projects are then deleted. A failed copy delete does not fail the request.
- Returns an empty body.
## Example
```bash
curl -X DELETE 'https://api.sandbox.ovok.com/v1/slim/policy/c3499c27-2973-0a7f-807e-fb8676a92dcb' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Update access policy](/api/slim/policy/update-access-policy): Replaces an access policy (role) of your project and sets exactly which practitioners hold it.
**Auth:** Bearer token for a practitioner, project admin or System Owner session. The caller's access policy must grant `Practitioner:create`, the right to invite users (a project admin who holds no access policy skips this check).
**Scope:** The policy must belong to the caller's project (from the token). Policies of parent or child projects can be read but not updated here.
## Behaviour
- `policy.id` must equal the `id` in the path.
- The policy body is replaced with `policy`. The same interactions as on create are added (`Consent`; `DeviceUseStatement` and `EpisodeOfCare` for policies that create or update `Patient`; `Provenance` and `AuditEvent` for `Admin` and `System Owner` policies).
- You cannot update a policy that ranks above you, nor rename one to a rank above you. Ranks: `System Owner`, then `Admin`, then every other policy.
- You cannot add or remove a member whose current role ranks above yours. A member ranks as every policy their membership holds now (`accessPolicy` and any `access` entries), each ranked by its name. A project admin (`admin: true`) ranks as System Owner.
- `members` is the complete list of holders, as references such as `Practitioner/`:
- a listed member's membership in your project now points to this policy, replacing the one it held;
- a current holder not listed loses the policy. Medplum reads a membership with no policy as full access, so if that would leave them with none (no `access` entries, not a project admin), the request answers 409: move them to another role first;
- you cannot remove yourself.
- The policy and the membership changes commit together. Past 49 membership changes they are written in two steps.
- The updated policy is then copied by name into each direct child project, also when it was renamed. A failed copy does not fail the request.
- Returns the updated policy with its members.
## Example
```bash
curl -X PUT 'https://api.sandbox.ovok.com/v1/slim/policy/c3499c27-2973-0a7f-807e-fb8676a92dcb' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{
"policy": {
"resourceType": "AccessPolicy",
"id": "c3499c27-2973-0a7f-807e-fb8676a92dcb",
"name": "Night shift",
"resource": [{ "resourceType": "Patient", "interaction": ["read", "search", "update"] }]
},
"members": [{ "reference": "Practitioner/c3499c27-2973-0a7f-807e-fb8676a92dcb" }]
}'
```
- Slim/Practitioner
- [Get practitioner](/api/slim/practitioner/get-practitioner): Returns a practitioner profile by id. Use it to show your own profile or, with the extra permission, another practitioner's.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Practitioner:read`. Reading another practitioner's profile also needs `Practitioner:update`. An admin or super admin who holds no access policy skips the access-policy check.
**Scope:** Your own profile is always readable. Another practitioner must belong to your project, one of its sub-projects, or its parent project.
## Behaviour
- Read-only.
- Your own profile is read with your token. Another practitioner's profile is read with Ovok's admin client after the project check above passes.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/practitioner/87ea5dfc-8b8e-384d-8489-79496e706390' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Update practitioner](/api/slim/practitioner/update-practitioner): Updates a practitioner profile: name, surname, email, phone, address and photo.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Practitioner:read`. Updating another practitioner's profile also needs `Practitioner:update`. An admin or super admin who holds no access policy skips the access-policy check.
**Scope:** You can always update your own profile. Another practitioner must belong to your project, one of its sub-projects, or its parent project.
## Behaviour
- The Practitioner resource is **replaced** with one built from the body. A field you omit is cleared, and FHIR fields that the body cannot carry are not kept. Send the full profile, for example the result of `GET /v1/slim/practitioner/{id}` with your changes applied.
- `id` in the body is optional. When present it must equal the `id` in the path.
- `meta` in the body is ignored. Ovok sets `meta.author` to the caller.
- `email` is stored in lower case.
- This route does not change roles or access policies.
- Your own profile is written with your token. Another practitioner's profile is written with Ovok's admin client after the project check above passes.
- The response is the updated profile.
## Example
```bash
curl -X PUT 'https://api.sandbox.ovok.com/v1/slim/practitioner/87ea5dfc-8b8e-384d-8489-79496e706390' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{
"name": "Max",
"surname": "Mustermann",
"email": "max.mustermann@example.com",
"phone": "+491234567890"
}'
```
- Slim/Project
- [List current project users](/api/slim/project/list-current-project-users): Lists the practitioner memberships of your current project, with each member's user, profile, access policy and invite status. Use it to render a project's team list.
**Auth:** Bearer token for a practitioner, project admin or System Owner session. The caller's access policy must grant `Practitioner:read` (a project admin who holds no access policy skips this check). The session must carry a project.
**Scope:** The caller's project (from the token).
## Behaviour
- Returns one row per `ProjectMembership` of `profileType` (`Practitioner`) that has both a user and a profile. Memberships whose user or profile cannot be resolved are left out.
- `status` is `Pending` while the user has an unused invite or has never set a password, and `Active` otherwise.
- `accessPolicyId`, `profileId` and `userId` narrow the memberships by exact id.
- `search` is a fuzzy match over the profile name, the user name and the access policy name.
- `sortBy` defaults to `profileName` and `orderBy` to `ASC`. Rows with an empty sort value go last in `ASC` and first in `DESC`.
- Paging is in memory: `page` (default `0`) and `count` (default `20`, 1 to 100). `total` counts the rows after `search`, before paging.
- Answers are cached for up to about a minute, so a member added or removed a moment ago can be missing or still listed.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/project/users?sortBy=userName&orderBy=ASC&page=0&count=20' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [List child project relationships](/api/slim/project/list-child-project-relationships): Returns your current project together with its parent project and its direct child projects. Use it to build a project switcher or hierarchy view.
**Auth:** Bearer token for a practitioner, project admin or System Owner session. The caller's access policy must grant `AccessPolicy:read` (a project admin who holds no access policy skips this check).
**Scope:** The caller's project (from the token).
## Behaviour
- `currentProject` is the caller's project.
- `parentProject` is the project your project is a child of, or `null` when it has none.
- `childProjects` lists every direct child, with no cap. Grandchildren are not included.
- A child whose project no longer exists is left out.
- Each project carries `name` (the project id when it has none), `address`, `contact` and `settings`. `settings.timeZone` defaults to `Europe/Berlin`; `settings.trendsPeriod` defaults to `start: "09:00:00"` and `durationSeconds: 86400`.
- `GET /v2/slim/project/child` returns the same lists with a device count per project.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/project/child' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Create a child project](/api/slim/project/create-a-child-project): Creates a child project under your current project and makes you its admin. Use it to set up a new customer or site below your organisation.
**Auth:** Bearer token for a project admin. No other role passes.
**Scope:** The new project becomes a direct child of the caller's project (from the token). A project that is itself a child may create children of its own.
## Behaviour
- Your project must already hold at least one access policy: the child gets a copy of every access policy of your project. Without one the request is refused with 424 and nothing is created.
- A second child with exactly the same `name` under the same parent is refused with 422.
- The whole create is all or nothing. A failure undoes what was written and answers the failure.
- On success Ovok:
- creates the project with practitioner invitations and practitioner login enabled, and practitioner registration plus all patient registration, invitation and login disabled;
- gives it its own Signals tenant and its own tenant code;
- links it to your project;
- copies your practitioner profile into it and adds you as an admin member;
- seeds its thresholds from the global defaults, with an out-of-bed window that ends at 09:00 and starts at a time picked once from 18:00 to 20:00 in 5-minute steps, so homes do not all start watching in the same minute. The start is stored and stays until someone saves another window;
- gives its Signals tenant the derived readings: each resident's daily mean heart rate and breathing rate (local midnight to midnight) and last night's sleep period, scheduled in the project's time zone.
- The organization stored inside the project is built from `name`, `address`, `contact` and `settings`. `settings.timeZone` must be an IANA time zone such as `Europe/Zurich` (any other value is refused with 422) and defaults to `Europe/Berlin`. `id` and `extension` in the body are ignored.
- When Signals cannot take the derived readings, the create is undone and answers 502, 503 when Signals is overloaded, or 504 when it does not answer in time.
- When the platform has it enabled, an Ovok support account is also added as a non-admin member holding the child's System Owner access policy. It is skipped when the child has no single policy of that name. This step never fails the create.
- Child project lists are refreshed right away.
- Returns `{ "projectId": "", "success": true }`.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/slim/project/child' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{
"name": "My Medical Project",
"contact": [{ "telecom": [{ "system": "email", "use": "work", "value": "max.mustermann@example.com" }] }],
"settings": { "timeZone": "Europe/Berlin" }
}'
```
```json
{ "projectId": "c3499c27-2973-0a7f-807e-fb8676a92dcb", "success": true }
```
- [List child projects with device counts](/api/slim/project/list-child-projects-with-device-counts): Returns your current project together with its parent project and its direct child projects, each with its device count. Use it for a project switcher or hierarchy view that shows fleet size.
**Auth:** Bearer token for a practitioner, project admin or System Owner session. The caller's access policy must grant `AccessPolicy:read` (a project admin who holds no access policy skips this check).
**Scope:** The caller's project (from the token).
## Behaviour
- Same lists as `GET /v1/slim/project/child`: `currentProject`, `parentProject` (`null` when there is none) and every direct child in `childProjects`. Grandchildren and children whose project no longer exists are left out.
- Every project also carries `deviceCount`: the number of `Device` resources in that project.
- `settings.timeZone` defaults to `Europe/Berlin`; `settings.trendsPeriod` defaults to `start: "09:00:00"` and `durationSeconds: 86400`.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v2/slim/project/child' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Delete a child project](/api/slim/project/delete-a-child-project): Deletes an empty direct child project of your current project, including the setup Ovok created with it. Use it to remove a child project that is no longer needed.
**Auth:** Bearer token for a project admin. No other role passes.
**Scope:** `subProjectId` must be a direct child of the caller's project (from the token).
## Behaviour
- The child must be empty: no `Patient`, `Device` or `Location`, and no child projects of its own. Otherwise the request is refused with 409 and nothing is changed. The 409 body has `error: "project_not_empty"`, a `counts` object per resource type and the `childProjectIds`.
- On success Ovok unlinks the child from your project, deletes its access policies, practitioners, memberships, threshold plan, client applications, top-level organizations and tenant code, deletes the project, then revokes its Signals tenant.
- User accounts are never deleted: a user can belong to other projects. Audit records (`Provenance`, `AuditEvent`) are kept.
- Two deletes of the same project cannot run at once: the second gets 409.
- The delete is safe to retry. If the Signals revoke fails, the project is already deleted and the request answers 502. Send the same request again to finish it.
- Returns `{ "success": true }`.
## Example
```bash
curl -X DELETE 'https://api.sandbox.ovok.com/v1/slim/project/child/c3499c27-2973-0a7f-807e-fb8676a92dcb' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [List project users](/api/slim/project/list-project-users): Lists the practitioner memberships of a given project: your own project or one of its direct child projects. Use it to manage the team of a child project from its parent.
**Auth:** Bearer token for a practitioner, project admin or System Owner session. The caller's access policy must grant `Practitioner:read` (a project admin who holds no access policy skips this check). The session must carry a project.
**Scope:** The project in the path. It must be the caller's project (from the token) or a direct child of it. Grandchildren are not reachable.
## Behaviour
- The project is read first, so an unknown or out-of-scope `id` fails before any membership is searched.
- Rows, filters, sorting, paging and `status` work exactly as in `GET /v1/slim/project/users`: one row per `Practitioner` membership with a user and a profile, `search` is fuzzy over profile, user and access policy names, `count` is 1 to 100 (default `20`).
- Answers are cached for up to about a minute.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/project/c3499c27-2973-0a7f-807e-fb8676a92dcb/users?count=50' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Get project device counts](/api/slim/project/get-project-device-counts): Returns summary counts of the devices in a project: total, assigned to a resident, active and by manufacturer. Use it for project overview tiles.
**Auth:** Bearer token. The caller's access policy must grant `Device:search` and `Device:read` (a project admin who holds no access policy skips this check).
**Scope:** The project in the path. It must be the caller's project (from the token) or a direct child of it.
## Behaviour
- Counts come from the project's live device list, the same rows that back the live device views. That list is cached, so a device change can take a moment to show.
- A row is left out when its device, resident, location or organization belongs to another project.
- `assignedResidentDeviceCount` counts devices with a resident; `unassignedDeviceCount` is the rest.
- `activeDeviceCount` counts devices whose latest telemetry reports them active; `inactiveDeviceCount` is the rest.
- `withingsDeviceCount` counts devices whose serial number resolves to Withings. Every other device is counted in `radarDeviceCount`.
- If the device list fails to build, the last complete list is used.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/project/c3499c27-2973-0a7f-807e-fb8676a92dcb/device-counts' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Get project patient counts](/api/slim/project/get-project-patient-counts): Returns summary counts of the residents in a project by state: active, recently discharged, discharged and archived. Use it for project overview tiles.
**Auth:** Bearer token. The caller's access policy must grant `Patient:search` and `Patient:read` (a project admin who holds no access policy skips this check).
**Scope:** The project in the path. It must be the caller's project (from the token) or a direct child of it.
## Behaviour
- Counts come from the project's live device list, so only residents linked to a device row are counted. A resident without a device is not included.
- Each resident is counted once, even when linked to several devices.
- A row is left out when its device, resident, location or organization belongs to another project.
- The device list is cached, so a change can take a moment to show. If the list fails to build, the last complete list is used.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/project/c3499c27-2973-0a7f-807e-fb8676a92dcb/patient-counts' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Get a project](/api/slim/project/get-a-project): Returns a project's name, address, contact and settings. Use it to show or edit your project or one of its child projects.
**Auth:** Bearer token. The caller's access policy must grant `AccessPolicy:read` and `AccessPolicy:update` (a project admin who holds no access policy skips this check).
**Scope:** The project in the path. It must be the caller's project (from the token) or a direct child of it.
## Behaviour
- `address` and `contact` come from the organization stored inside the project.
- `name` falls back to the project id when the project has no name.
- `settings.timeZone` defaults to `Europe/Berlin` when none is stored.
- `settings.trendsPeriod` defaults to `start: "09:00:00"` and `durationSeconds: 86400` when none is stored.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/project/c3499c27-2973-0a7f-807e-fb8676a92dcb' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Update a project](/api/slim/project/update-a-project): Updates a project's name, address, contact and settings, and returns the updated project. Use it to edit your project or one of its child projects.
**Auth:** Bearer token of a project admin (`ProjectMembership.admin`).
**Scope:** The project in the path. It must be the caller's project (from the token) or a direct child of it.
## Behaviour
- Sets the project `name` and replaces the organization stored inside the project with one built from `name`, `address`, `contact` and `settings`. This is a full replace, not a merge:
- an omitted `address` or `contact` clears it;
- an omitted `settings.timeZone` resets the time zone to `Europe/Berlin`;
- an omitted `settings.trendsPeriod` removes the stored trends period, so reads fall back to the default.
- `id` and `extension` in the body are ignored. The project comes from the path.
- `settings.trendsPeriod.start` must be `HH:MM:SS`.
- The response has the shape of `GET /v1/slim/project/{id}`.
## Example
```bash
curl -X PUT 'https://api.sandbox.ovok.com/v1/slim/project/c3499c27-2973-0a7f-807e-fb8676a92dcb' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{
"name": "My Medical Project",
"address": [{ "line": ["Hauptstrasse 1"], "city": "Berlin", "postalCode": "10115", "country": "DE" }],
"settings": { "timeZone": "Europe/Berlin", "trendsPeriod": { "start": "09:00:00", "durationSeconds": 86400 } }
}'
```
- Slim/Query
- [List devices with telemetry (compatibility)](/api/slim/query/list-devices-with-telemetry-compatibility): Compatibility route for older query clients. Returns device rows joined with their resident, location, zone and telemetry. For new integrations, use `GET /v1/slim/device/live` or the `/v1/slim/patient` routes.
**Auth:** bearer token. The caller must be a practitioner, an admin or a System Owner, and needs `search` and `read` on `Patient`, `Device`, `Location`, `Observation` and `Organization`.
**Scope:** the caller's project (from the token). The `project` parameter is ignored.
## Behaviour
- `mustInclude` lists the resources a row must have to be returned. `shouldInclude` lists the resources added to each row when present. Both are comma-separated lists of `organization`, `device`, `location`, `patient` and `telemetry`.
- Every entry in `mustInclude` must also be in `shouldInclude`. `telemetry` in either list needs `device` in the same list.
- When `mustInclude` contains `patient`, the query runs on the older resident-oriented path, which reads telemetry from the device database and Redis. Otherwise it runs on the same path as `GET /v1/slim/device/live`.
- Without `includeUnassigned=true`, rows without a resident are left out when `patient` is required.
- `organization` takes `Organization/`, `assigned` or `unassigned`.
- `page` is zero-based. `count` defaults to 20, maximum 100.
- The route returns 503 when the server is at capacity for live reads or the device list cannot be built. Retry shortly.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/query?mustInclude=device,telemetry&shouldInclude=organization,device,location,patient,telemetry&page=0&count=20' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [List telemetry and alerts (compatibility)](/api/slim/query/list-telemetry-and-alerts-compatibility): Compatibility route for older live polling clients. Returns telemetry and alerts maps keyed by device identifier. For new integrations, use `GET /v1/slim/device/live/telemetry`.
**Auth:** bearer token. The caller must be a practitioner, an admin or a System Owner, and needs `search` and `read` on `Patient`, `Device`, `Location`, `Observation` and `Organization`.
**Scope:** the caller's project (from the token). The `project` parameter is ignored.
## Behaviour
- Takes the same query parameters as `GET /v1/slim/query`, with the same validation rules.
- Returns `total`, plus `telemetry` and `alerts` for the requested page only.
- `alerts` has an entry for every device on the page. `telemetry` has an entry only for devices with telemetry.
- The route returns 503 when the server is at capacity for live reads or the device list cannot be built. Retry shortly.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/query/telemetry?mustInclude=device,telemetry&shouldInclude=device,telemetry' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Get resident devices with telemetry (compatibility)](/api/slim/query/get-resident-devices-with-telemetry-compatibility): Compatibility route for older resident vitals clients. Returns the devices assigned to one resident, each with its telemetry. For new integrations, use `GET /v1/slim/patient/{id}/current-vitals`.
**Auth:** bearer token. The caller must be a practitioner, an admin or a System Owner, and needs `search` and `read` on `Patient`, `Device`, `Location`, `Observation` and `Organization`.
**Scope:** the caller's project (from the token). The `project` parameter is ignored.
## Behaviour
- Returns up to 100 device rows for the resident, with zone, device and telemetry.
- Results are cached for up to 60 seconds. Writes made through the Slim routes refresh the cache sooner.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/query/patient/c3499c27-2973-0a7f-807e-fb8676a92dcb' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Count devices per zone (compatibility)](/api/slim/query/count-devices-per-zone-compatibility): Compatibility route for older zone count clients. Returns device counts per zone. For new integrations, use `GET /v1/slim/zones/device-counts`.
**Auth:** bearer token. The caller must be a practitioner, an admin or a System Owner, and needs `Organization:search` and `Device:search`.
**Scope:** the caller's project (from the token). For admins, the counts also cover the project's sub-projects.
## Behaviour
- Each entry has the zone (`organization`) and its `deviceCount`, `assignedResidentDeviceCount`, `activeDeviceCount` and `inactiveDeviceCount`.
- Devices without a zone are counted in an entry without `organization`.
- Only zones with at least one device are listed, sorted by `deviceCount`, highest first.
- A device counts as active when its telemetry reports it active.
- The top-level `total*` fields sum the counts over all entries. `total` is the number of entries.
- Results are cached for up to 60 seconds. Writes made through the Slim routes refresh the cache sooner.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/query/organization/device/count' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [List users (compatibility)](/api/slim/query/list-users-compatibility): Compatibility route for older user list clients. Lists the practitioner users of a project with their profile, membership and access policy. For new integrations, use `GET /v1/slim/project/users`.
**Auth:** bearer token. The caller must be a practitioner, an admin or a System Owner, and needs `Practitioner:read`.
**Scope:** the caller's project (from the token). When you pass `project=Project/`, that project is used instead. It must be your project or one of its sub-projects.
## Behaviour
- `profileType` is required and must be `Practitioner`.
- Each row has `user`, `profile`, `membership` and, when set, `accessPolicy`, reduced to the fields a list needs.
- A user's status is `Pending` when they have an unused invite or no password yet, otherwise `Active`.
- `accessPolicy`, `profile` and `user` filter on exact references, for example `AccessPolicy/`.
- `search` matches profile name, user name and access policy name, with fuzzy matching.
- `page` is zero-based. `count` defaults to 20, maximum 100.
- Results are cached for up to 60 seconds.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/query/user?profileType=Practitioner&page=0&count=20' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [List users across projects (compatibility)](/api/slim/query/list-users-across-projects-compatibility): Compatibility route for older cross-project user list clients. Behaves exactly like `GET /v1/slim/query/user`. For new integrations, use `GET /v1/slim/project/{id}/users`.
**Auth:** bearer token. The caller must be a practitioner, an admin or a System Owner, and needs `Practitioner:read`. No extra admin check is applied.
**Scope:** the caller's project (from the token), or the project in `project=Project/` when it is your project or one of its sub-projects.
## Behaviour
- Same parameters, filters, sorting, paging and caching as `GET /v1/slim/query/user`.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/query/admin/user?profileType=Practitioner&project=Project/c3499c27-2973-0a7f-807e-fb8676a92dcb' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- Slim/Sleep
- [Get sleep statistics](/api/slim/sleep/get-sleep-statistics): Returns a resident's sleep statistics for one night, grouped by device, with each metric compared against the average of the preceding nights. Use it for the sleep detail view.
**Auth:** bearer token. The caller must be a practitioner, an admin or a System Owner, and needs `Observation:read` and `Observation:search`.
**Scope:** the caller's project (from the token). Sleep data is read with your token, so Medplum's access policy limits what you see.
## Behaviour
- The "night" is the calendar day, in the project's time zone, of `endDate`. A sleep recording belongs to the day its `effectivePeriod.end` falls on.
- `lastDayCount` is how many earlier days feed `average`. It defaults to `7` and must be between 1 and 30.
- Each device entry holds, per metric, `current` (the night of `endDate`), `average` (over the earlier nights that have a recording) and `diff`. A device with no recording for the night of `endDate` is left out.
- When one night has several recordings, the one written last is used: the latest `meta.lastUpdated`. Its period and completeness do not count, so a shorter recording written later wins, and so can a same-day nap ending after the night. A metric it does not carry is `null`, never taken from another recording. The PDF export's sleep table uses the same rule, and so do the `GET /v1/slim/trends/graphs` charts where they can match a night's readings to its recordings.
- `average` and `diff` use each earlier night's latest-written recording the same way.
- Only recordings that end inside one of the resident's use periods of their device are counted, before a night's recording is picked. So a previous resident's night that ended before the device was handed over, but reached Ovok after it and was stored under this resident, is not shown. A resident with no device use history has all recordings counted. The PDF export and the `GET /v1/slim/trends/graphs` charts apply the same filter.
- Once a resident has any use period, recordings of a device with no period of theirs, and those from before a device's earliest period, are not counted. A device assigned before use periods were recorded gets its first one on its next edit in Ovok, starting at the Device's last write before that edit. Each device whose recordings in a request all drop is counted in the `sleep.device_use_device_dropped` metric and logged at most once a minute.
- Ovok reads the `sleep-stats` observations first and falls back to the LOINC pair `93832-4` (sleep duration) and `103216-8` (out-of-bed count) when there are none.
- The response is `{}` when no qualifying recording exists.
- Results are cached for up to 60 seconds per project, resident, `endDate` and `lastDayCount`. A write to the resident's data invalidates the cache sooner.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/sleep/statistics?patientId=c3499c27-2973-0a7f-807e-fb8676a92dcb&endDate=2026-09-28T00:00:00Z&lastDayCount=7' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- Slim/Telemetry
- [List effective observation ids by device](/api/slim/telemetry/list-effective-observation-ids-by-device): Returns the effective Observation ids for every device in your project, keyed by device identifier. These are the readings that produced each device's current heart rate, respiratory rate, sleep status and presence values.
**Auth:** bearer token. The caller must be a practitioner, an admin or a System Owner, and needs `Device:read` and `Device:search`.
**Scope:** the caller's project (from the token).
## Behaviour
- Covers the devices that have telemetry, up to the first 1000 devices of the project, including devices without a resident.
- Each value has the same shape as `GET /v1/slim/telemetry/{deviceId}/effectiveIds`. Fields are `null` when no reading has set them.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/telemetry/effectiveIds' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [List last-received observation ids by device](/api/slim/telemetry/list-last-received-observation-ids-by-device): Returns the last-received Observation ids for every device in your project, keyed by device identifier.
**Auth:** bearer token. The caller must be a practitioner, an admin or a System Owner, and needs `Device:read` and `Device:search`.
**Scope:** the caller's project (from the token).
## Behaviour
- Covers the devices that have telemetry, up to the first 1000 devices of the project, including devices without a resident.
- Each value has the same shape as `GET /v1/slim/telemetry/{deviceId}/lastIds`. Fields are `null` when nothing has been received for them.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/telemetry/lastIds' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Get effective observation ids for a device](/api/slim/telemetry/get-effective-observation-ids-for-a-device): Returns, for one device, the ids of the Observations that produced its current heart rate, respiratory rate, sleep status and presence values. Use it to trace a displayed value back to its source reading.
**Auth:** bearer token. The caller must be a practitioner, an admin or a System Owner, and needs `Device:read`.
**Scope:** a device identifier only resolves when the device is visible to your token and in the caller's project.
## Behaviour
- `deviceId` is either the device identifier or the Device resource id (a UUID).
- Each field is an Observation id, or `null` when no reading has set that value yet.
- Only devices whose readings carry their own Observation id fill these fields. For other devices every field is `null`.
- A device that is not found returns every field `null`, not an error.
- Lookups of a device are cached for up to 10 minutes, and misses for 30 seconds.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/telemetry/c3499c27-2973-0a7f-807e-fb8676a92dcb/effectiveIds' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Get last-received observation ids for a device](/api/slim/telemetry/get-last-received-observation-ids-for-a-device): Returns, for one device, the ids of the most recent Observations received for heart rate, respiratory rate, sleep status and presence, plus the latest batch. Unlike `effectiveIds`, these change on every reading, whether or not the displayed value changed.
**Auth:** bearer token. The caller must be a practitioner, an admin or a System Owner, and needs `Device:read`.
**Scope:** a device identifier only resolves when the device is visible to your token and in the caller's project.
## Behaviour
- `deviceId` is either the device identifier or the Device resource id (a UUID).
- Each field is an Observation id, or `null` when nothing has been received for it yet.
- Only devices whose readings carry their own Observation id fill these fields. For other devices every field is `null`.
- A device that is not found returns every field `null`, not an error.
- Lookups of a device are cached for up to 10 minutes, and misses for 30 seconds.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/telemetry/c3499c27-2973-0a7f-807e-fb8676a92dcb/lastIds' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Get device telemetry and alerts](/api/slim/telemetry/get-device-telemetry-and-alerts): Returns the current telemetry and alerts for one device. Use it to poll the resident detail view.
**Auth:** bearer token. The caller must be a practitioner, an admin or a System Owner, and needs `Device:read`.
**Scope:** the caller's project (from the token). Only devices in that project are found.
## Behaviour
- `deviceId` is either the device identifier (for example `Sleepiz/3001000`) or the Device resource id (a UUID). A UUID is resolved to the identifier.
- The response `deviceId` is the identifier the telemetry is keyed by.
- `telemetry` and `alerts` have the same shape as one entry of `GET /v1/slim/device/live/telemetry`.
- The device is looked up among the first 1000 devices of your project, including devices without a resident.
- Telemetry is read fresh on every request.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/telemetry/c3499c27-2973-0a7f-807e-fb8676a92dcb' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- Slim/Threshold
- [Get current threshold](/api/slim/threshold/get-current-threshold): Returns the home's threshold configuration: the alert limits every resident follows unless they have limits of their own.
> **Deprecated.** Use `GET /v2/slim/threshold`. This v1 route stays available, unchanged, until every client has moved.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `PlanDefinition:read` (an admin or super admin who holds no access policy skips this check).
**Scope:** The caller's project (from the token).
## Behaviour
- Read-only.
- A home that has never saved thresholds gets the default template, with no `id`: heart rate on at 40–110 and respiratory rate on at 8–25 (the limits a new home is seeded with), and every other metric disabled.
- If the stored thresholds cannot be read, the route answers 502, never the default template.
- Units: `durationOfOutOfBed` is in seconds, `sleepDuration` in hours.
- The home's limit ranges and the optional vitals (SpO₂, pulse rate, perfusion index, body temperature) are only on the v2 route.
- Heart-rate and respiratory-rate limits are evaluated by Signals. Ovok stores them and forwards them to Signals.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/threshold' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Update threshold](/api/slim/threshold/update-threshold): Replaces the home's threshold configuration: the alert limits every resident follows unless they have limits of their own.
> **Deprecated.** Use `PUT /v2/slim/threshold`. This v1 route stays available until every client has moved.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `PlanDefinition:update` (an admin or super admin who holds no access policy skips this check).
**Scope:** The caller's project (from the token).
## Behaviour
- The body replaces the stored settings; fields you omit take their schema defaults, except the out-of-bed window: a body without `durationOfOutOfBed.period`, or without `durationOfOutOfBed`, keeps the home's stored window (19:00:00 to 09:00:00 when it has none). The home's limit ranges and optional vitals, which v1 cannot send, are kept as stored.
- Units: `durationOfOutOfBed` is in seconds, `sleepDuration` in hours.
- `enabled: false` switches a metric fully off: its `absolute` and `relative` bands are stored off too, whatever their own `enabled` says, and the residents following the home hold no Signals limit for it (a resident with limits of their own keeps them). Out of bed keeps its `relative` band, which only tightens its limit while it is on.
- To switch a metric back on, send its band's `enabled: true` as well. A switch-off stores the band's flag `false`, so a body that sets only the metric's `enabled: true` reads back `enabled: true` but arms nothing: the residents following the home get no limit for it.
- `meanHeartRate` and `meanRespiratoryRate` relative bands cannot be enabled for the home: a resident without a baseline of their own would get no alarm from them. Set baseline limits per resident. One sent under `enabled: false` is stored off.
- Every limit is evaluated by Signals, so every save is all or nothing, as `PUT /v2/slim/threshold` describes: it answers once Signals holds the home's limits, which every resident following the home follows at once, or puts everything back and answers an error with `saved: false` (409 another save or a tenant move got in the way; 502 out of time, or Signals did not take them; 500 the undo failed too). Nothing is stored then; send the save again. A 504 from the gateway can come while a failed save is still being put back: read the limits before sending it again.
- A change-history entry is written for a save that stands.
- The response is the saved configuration.
## Example
```bash
curl -X PUT 'https://api.sandbox.ovok.com/v1/slim/threshold' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{
"status": "active",
"settings": {
"heartRate": { "mode": "organization", "enabled": true, "absolute": { "enabled": true, "min": 40, "max": 120 } }
}
}'
```
- Slim/Trends
- [Get trends graphs](/api/slim/trends/get-trends-graphs): Returns a resident's heart rate and respiratory rate over a date range, grouped by graph type and device, with threshold and baseline values and summary statistics. Use it to draw the trend charts. For live readings from the last day, use `GET /v1/slim/trends/realtime`.
**Auth:** bearer token. The caller must be a practitioner, an admin or a System Owner, and needs `Organization:read`, `Device:read`, `Patient:read` and `DocumentReference:read`.
**Scope:** the caller's project (from the token). Observations are read with your token, so Medplum's access policy limits what you see.
## Behaviour
- `graphType` is a comma-separated list of `heartRate` and `respiratoryRate`. Repeated entries are ignored. `startDate` must not be after `endDate`.
- Days are cut at the project's time zone (default `Europe/Berlin`). The chart for a day starts at 09:00 local time, so a night stays in one day.
- A range of two or more days is an over-day chart: one point per day, at 09:00 local time, starting the day after `startDate`. Each point is the day's mean from the pre-computed statistics, with batch readings filling nights that have none.
- A range of one day or less is an in-day chart: 30-second points across the 24 hours that start at 09:00 local time on `startDate`. `endDate` only decides which kind of chart you get. In-day points come from batch readings only.
- Before a night's recording is picked, both charts drop the readings and sleep statistics that end outside the resident's use periods of their device, as `GET /v1/slim/sleep/statistics` does. So a previous resident's night that ended before the device was handed over, but reached Ovok after it and was stored under this resident, is not charted. A resident with no device use history keeps all of them.
- Once a resident has any use period, this also drops the readings of a device with no period of theirs, and those from before a device's earliest period. A device assigned before use periods were recorded gets its first one on its next edit in Ovok, starting at the Device's last write before that edit, so its readings from before then are not charted. Each device whose readings in a request all drop is counted in the `sleep.device_use_device_dropped` metric and logged at most once a minute.
- The use periods are read on every uncached call. When Medplum answers that read with an error, the route answers `502` rather than chart a night it cannot check; when the request itself fails, the route fails with Medplum's status, or `500`.
- When a night (the calendar day its recordings end on, in the project's time zone) has several sleep recordings left, both charts show only the readings of the one whose sleep statistics were written last (latest `meta.lastUpdated`): its heart and respiratory rate statistics, matched by the recording's exact period, and its batch readings, matched by their start (below). That is the rule `GET /v1/slim/sleep/statistics` uses. A night with no sleep statistics goes by its heart and respiratory rate statistics' write times. A night with one recording left, a night with batch readings only, and a night none of whose readings belongs to the chosen recording are charted with all the readings left. Readings with no end time cannot be put on a night, so neither rule applies to them: they are always charted.
- Sleep statistics are read over the chart's range, which ends at 09:00 local time on its last day, so a recording that starts later that day is not weighed.
- A batch block is matched by its start, because Sleepiz ends one up to about ten minutes before its recording: it belongs to the recording that starts at the same instant and ends at or after it, at most 15 minutes later, the earliest such end when two recordings share the start. A batch block that starts at any other time belongs to no recording, so on a night with several recordings it is dropped: a chosen recording whose only heart rate reading is a batch block that starts after the recording shows no heart rate for that night, while its respiratory rate still shows.
- Every bucket is returned. A bucket without a reading has `data: null`. `pointTimestamp` and `pointTimestampRange` show which readings filled it.
- `maxThreshold`, `minThreshold`, `maxBaseLine` and `minBaseLine` are the values that applied at that time. They are left out when none apply, and when the threshold lookup fails the chart still returns without them.
- When a heart or breathing rate limit changed inside the range (a nurse's edit or a home-wide save), each point carries the limit in force at its own time, as Signals recorded it, so the lines step where the limit changed. Points after the last change carry the limits the limits screen shows. If Signals' change history cannot be read, every point carries the current limits.
- Devices are keyed `-`. A resident who changed device has one entry per device.
- A graph type with no data in the range comes back as `{}`. When nothing at all can be charted, both `heartRate` and `respiratoryRate` are `{}`.
- Results are cached for up to 60 seconds. A write to the resident's data invalidates the cache sooner.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/trends/graphs?patientId=c3499c27-2973-0a7f-807e-fb8676a92dcb&startDate=2026-09-01T00:00:00Z&endDate=2026-09-30T00:00:00Z&graphType=heartRate,respiratoryRate' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
```json
{
"heartRate": {
"Sleepiz-5f0c...": {
"dataPoints": [
{
"timestamp": "2026-09-02T07:00:00.000Z",
"timestampMs": 1788332400000,
"data": 72,
"maxThreshold": 100,
"minThreshold": 60,
"pointTimestamp": "2026-09-02T05:41:00.000Z"
}
],
"stats": { "average": 72, "max": 72, "min": 72, "total": 1 }
}
},
"respiratoryRate": {}
}
```
- [Get realtime trends graphs](/api/slim/trends/get-realtime-trends-graphs): Returns the resident's readings from Signals, one data point per reading, in the same shape as `GET /v1/slim/trends/graphs`. Use it for the live part of a chart, or to look at any day of the last 14.
**Auth:** bearer token. The caller must be a practitioner, an admin or a System Owner, and needs `Organization:read`, `Device:read`, `Patient:read` and `DocumentReference:read`.
**Scope:** the resident must be in the caller's project or one of its sub-projects (not its parent). A resident outside that scope answers `404`, the same as one that does not exist.
## Behaviour
- The chart covers exactly `[startTime, endTime]`. Unlike `/graphs`, it does not snap to monitoring days.
- Every reading in the window comes back as Signals recorded it, oldest first: one data point per reading, at the reading's own time, with its own value. Nothing is bucketed or averaged, and no empty points are added between readings, so a silent device shows as a stretch with no points. A reading with no numeric value comes back with `data: null`.
- The response grows with the device's cadence: a day of readings every 30 seconds is about 2,900 points per graph type.
- The readings are the resident's, whatever device they wear now: a resident whose device was unassigned or moved to someone else, or who was discharged, still gets every reading Signals holds for them in the window. A resident who takes over a device gets only the readings sent after they took it.
- Readings are charted under one key: the resident's most recently assigned device, keyed as on `/graphs`. A resident with no device now is charted under the device they wore last, so the key does not change when the device is taken away, and a resident who never wore one under `Patient/`. Signals readings carry no device, so a resident whose device changed inside the window shows one key here where `/graphs` shows one per device.
- Threshold and baseline values are the same as `/graphs`. `stats` are computed over every reading in the window.
- Every graph type in the table below can be charted. Only `heartRate` and `respiratoryRate` carry threshold and baseline values. Each point carries the limit in force at its own time, as on `/graphs`, so the lines step where a limit changed inside the window.
- `startTime` must not be after `endTime`, the window may span at most 24 hours, and `startTime` must not be more than 14 days ago. A request that breaks one of these fails validation.
- Readings are charted by when they were observed, or by when Signals stored them when no observed time exists. Late-arriving readings are included.
- A graph type with no readings in the window is `{}`.
- If Signals cannot be reached, does not answer within 12 seconds, or holds more readings than one request can page through, the route returns `503` with `error: "signals_unavailable"` and a `retryAfterSec` value, rather than a partial or empty chart.
- Results are cached for up to 15 seconds. Both request times are rounded down to that interval, and the readings returned are those of the rounded window, so a repeated poll can show a window up to 15 seconds old. A result over 128 KB (most full days) is not cached.
- A chart reads this once per page load. Each caller may make 20 requests a minute; past that the route returns `429` with `Retry-After`. When too many realtime reads are already running on the server, it returns `503` with `error: "signals_unavailable"` and `retryAfterSec` at once, before reading anything.
## Graph types
Each type charts every Signals reading recorded under one of its codes. The names are the dashboard telemetry's, so
pulse rate by pulse oximetry is `pulseRate` and never part of `heartRate`.
| `graphType` | Codes |
| ------------------------ | ---------------------------------------------------------------- |
| `heartRate` | `8867-4`, `43149-4` |
| `respiratoryRate` | `9279-1` |
| `oxygenSaturation` | `59408-5`, `2708-6` |
| `pulseRate` | `8889-8` (heart rate by pulse oximetry) |
| `perfusionIndex` | `61006-3`, `PI` (Kinderspitex's own vital code, not LOINC) |
| `bodyTemperature` | `8310-5` |
| `systolicBloodPressure` | `8480-6` |
| `diastolicBloodPressure` | `8462-4` |
| `heartRateVariability` | `80404-7` |
## Example cURL request
```bash
curl -X GET \
'https://api.sandbox.ovok.com/v1/slim/trends/realtime?patientId=c3499c27-2973-0a7f-807e-fb8676a92dcb&startTime=2026-01-01T12:00:00Z&endTime=2026-01-02T12:00:00Z&graphType=heartRate,respiratoryRate' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- Slim/User
- [Get terms and conditions consent](/api/slim/user/get-terms-and-conditions-consent): Returns whether the signed-in practitioner has accepted the current terms and conditions. Use it to decide whether to show the terms prompt.
**Auth:** Bearer token for a practitioner session (the profile must be a `Practitioner`). The caller's access policy must grant `Consent:read` and `Consent:search` (a project admin who holds no access policy skips this check).
**Scope:** The caller's practitioner profile in the caller's project (from the token).
## Behaviour
- `termsAndConditionsConsent` is `true` only when an acceptance is on file and either matches the currently published terms version or no version has been published yet.
- Publishing new terms turns it `false` for everyone until they accept again.
- The published version is the one of the root project of your project tree: a child project follows its parent's terms. Without one, the platform-wide version applies.
- `GET /v1/me/consent/legal` returns the same acceptance with its dates and terms link.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/user/terms-and-conditions-consent' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Update terms and conditions consent](/api/slim/user/update-terms-and-conditions-consent): Accepts or withdraws the terms and conditions for the signed-in practitioner. Other consents are not changed.
**Auth:** Bearer token for a practitioner session (the profile must be a `Practitioner`). The caller's access policy must grant `Consent:read`, `Consent:search`, `Consent:create` and `Consent:update` (a project admin who holds no access policy skips this check).
**Scope:** The caller's practitioner profile in the caller's project (from the token).
## Behaviour
- `true` records acceptance of the currently published terms version. `false` withdraws it.
- The acceptance is also kept as a legal `Consent` in your project: accepting creates or re-activates it with a link to the terms page of your project's practitioner app; withdrawing revokes it with reason `withdrawn`.
- Repeating the same value writes nothing. The call is idempotent.
- The consent answers of `/v1/me/consent/*` are refreshed right away.
- Returns the resulting `termsAndConditionsConsent`.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/slim/user/terms-and-conditions-consent' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{ "termsAndConditionsConsent": true }'
```
- [Get all user consents](/api/slim/user/get-all-user-consents): Returns every consent of the signed-in practitioner in one response. Use it at sign-in to decide which consent prompts to show.
**Auth:** Bearer token for a practitioner session (the profile must be a `Practitioner`). The caller's access policy must grant `Consent:read` and `Consent:search` (a project admin who holds no access policy skips this check).
**Scope:** The caller's practitioner profile in the caller's project (from the token).
## Behaviour
- Both `medicalSettingsNotificationsConsent` and `termsAndConditionsConsent` are versioned. Each is `true` only when an acceptance is on file and either matches the currently published version of that consent or no version has been published yet.
- Publishing a new version of a consent turns it `false` for everyone until they accept again.
- The published version is the one of the root project of your project tree. Without one, the platform-wide version applies.
- A consent with no record reads `false`.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/user/consents' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
```json
{ "medicalSettingsNotificationsConsent": true, "termsAndConditionsConsent": false }
```
- [Update user consents](/api/slim/user/update-user-consents): Updates one or more consents of the signed-in practitioner. Use it to save the answers of a combined consent prompt.
**Auth:** Bearer token for a practitioner session (the profile must be a `Practitioner`). The caller's access policy must grant `Consent:read`, `Consent:search`, `Consent:create` and `Consent:update` (a project admin who holds no access policy skips this check).
**Scope:** The caller's practitioner profile in the caller's project (from the token).
## Behaviour
- Send at least one of `medicalSettingsNotificationsConsent` and `termsAndConditionsConsent`. Only the fields you send change.
- `true` records acceptance of the currently published version of that consent. `false` withdraws it.
- A `termsAndConditionsConsent` change also creates, re-activates or revokes the legal `Consent` read by `GET /v1/me/consent/legal`.
- All changes commit together or not at all. Repeating the same values writes nothing.
- The consent answers of `/v1/me/consent/*` are refreshed right away.
- The response always carries the resulting state of every consent.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/slim/user/consents' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{ "medicalSettingsNotificationsConsent": true, "termsAndConditionsConsent": true }'
```
- [Send support message](/api/slim/user/send-support-message): Sends a support email on behalf of the signed-in user to the support address configured for this Ovok environment.
**Auth:** Bearer token for a practitioner, project admin or System Owner session. The caller's access policy must grant `Patient:read`, `Patient:search`, `Device:read`, `Device:search`, `Observation:read` and `Observation:search` (a project admin who holds no access policy skips this check).
**Scope:** The caller's project (from the token).
## Behaviour
- Sends one email built from the `message`, `version` and `environment` you send.
- The email also carries details taken from your session, not from the body: your name, email, project name, profile type, access policy name and tenant code.
- Nothing else is written.
- Returns `true` once the email is sent.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/slim/user/support' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{ "message": "The live view does not load.", "version": "1.8.0", "environment": "production" }'
```
- [Delete user](/api/slim/user/delete-user): Removes a practitioner from a project: deletes their membership and practitioner profile, and their user account when this was their last membership. Use it to offboard a team member.
**Auth:** Bearer token for a practitioner, project admin or System Owner session. The caller's access policy must grant `Practitioner:delete` (a project admin who holds no access policy skips this check).
**Scope:** The practitioner's project must be the caller's project (from the token) or a direct child of it.
## Behaviour
- `profileType` must be `Practitioner` and `profileId` a UUID.
- The membership, the practitioner and, when it has no other membership, the user are deleted together or not at all.
- The owner of the caller's own project cannot be deleted. The owner of a child project can.
- After the delete, an account-deletion email is sent to the user's email address, if they have one. A failed email does not fail the request.
- Returns `{ "success": true }`.
## Example
```bash
curl -X DELETE 'https://api.sandbox.ovok.com/v1/slim/user/Practitioner/c3499c27-2973-0a7f-807e-fb8676a92dcb' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- Slim/Zones
- [List zones](/api/slim/zones/list-zones): Lists the zones of your project with their device counts. A zone is a FHIR `Organization`. Use it for a zone overview; use `GET /v1/slim/zones/{zoneId}` for one zone's locations.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Organization:search`, `Organization:read` and `Device:read` (an admin or super admin who holds no access policy skips this check).
**Scope:** The caller's project (from the token).
## Behaviour
- Counts are taken from the same rows as `GET /v1/slim/device/live`: only devices assigned to a resident are counted. `deviceCount` is the number of such devices in the zone; `activeDeviceCount` and `inactiveDeviceCount` split them by whether the device is currently active, as on the live view.
- Only zones with at least one counted device are listed. A zone with no assigned devices does not appear; `GET /v1/slim/zones/device-counts?includeEmpty=true` lists it.
- `search` keeps zones whose name contains the text, ignoring case.
- `sortBy`: `name` (default), `deviceCount`, `activeDeviceCount` or `inactiveDeviceCount`. `orderBy`: `ASC` (default) or `DESC`.
- Paging: `page` from 0, `count` from 1 to 100 (default 20). `total` is the number of zones after the search filter.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/zones?sortBy=deviceCount&orderBy=DESC&page=0&count=20' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Get zone device counts](/api/slim/zones/get-zone-device-counts): Returns device counts per zone for your project, with project-wide totals. A zone is a FHIR `Organization`.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Organization:search`, `Organization:read`, `Device:search` and `Device:read` (an admin or super admin who holds no access policy skips this check).
**Scope:** The caller's project (from the token).
## Behaviour
- By default, `deviceCount` matches the rows `GET /v1/slim/device/live` returns for each zone: devices assigned to a resident.
- `all=true` counts every device in each zone, assigned or not, and ignores `effectiveSleepStatus`.
- `effectiveSleepStatus` (`disconnected`, `connected`, `awake`, `away`, `asleep`) counts only devices in that state. A device that is not active counts as `disconnected`.
- `includeUnassigned=true` also counts devices with no resident (unless `all=true` already does) and adds a bucket, with `organization` unset, for devices in no zone.
- `includeEmpty=true` keeps project zones that have no matching devices, with zero counts.
- For each zone, `assignedResidentDeviceCount` counts devices with a resident, and `activeDeviceCount` / `inactiveDeviceCount` split devices by whether they are currently active.
- Zones are sorted by `deviceCount`, highest first. `total` is the number of entries; the `total…Count` fields sum the per-zone counts.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/zones/device-counts?includeEmpty=true&includeUnassigned=true' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Get zone detail](/api/slim/zones/get-zone-detail): Returns one zone with all of its locations and the devices and residents assigned to each location. A zone is a FHIR `Organization`. Use it for a zone drill-down view.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Organization:read` and `Location:read` (an admin or super admin who holds no access policy skips this check).
**Scope:** The zone must belong to the caller's project (from the token), its parent project, or one of its sub-projects.
## Behaviour
- Returns every location whose managing organization is the zone; there is no paging.
- Each location carries `floor`, `room` and `bed` when set, and its `devices` and `residents`. Devices and residents are sorted by name in natural order (so "Bed 2" sorts before "Bed 10").
- A location with no devices has empty `devices` and `residents` lists.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/zones/c3499c27-2973-0a7f-807e-fb8676a92dcb' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Create location in zone](/api/slim/zones/create-location-in-zone): Creates a location (a FHIR `Location`) inside a zone. The new location's managing organization is the zone.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Location:create` and `Device:update` (an admin or super admin who holds no access policy skips this check).
**Scope:** The location is created in the caller's project (from the token). A super-admin caller may pass `?projectId=` to create it in that project instead; the project must exist. The zone must belong to the project in use, its parent project, or one of its sub-projects.
## Behaviour
- `name` is required and must be unique among the locations your token can search.
- `floor`, `room` and `bed` are optional. When at least one is set, no other location in the zone may have the same floor, room and bed. A location with none of them is checked by name only.
- The location is created with no devices or residents. Assign them with `PUT /v1/slim/zones/{zoneId}/locations/{locationId}`.
- A location-created entry is added to the change history. A failure to write that entry does not fail the request.
- The response is the new location, in the same shape as `GET /v1/slim/location/{id}`.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/slim/zones/c3499c27-2973-0a7f-807e-fb8676a92dcb/locations' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{
"name": "Room 101 Bed 1",
"floor": "1",
"room": "101",
"bed": "1"
}'
```
- [Delete location in zone](/api/slim/zones/delete-location-in-zone): Deletes an empty location from a zone, or, with `dryRun=true`, previews which devices and residents are at it without changing anything.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Location:update` and `Device:update` (an admin or super admin who holds no access policy skips this check).
**Scope:** The zone must belong to the caller's project (from the token), its parent project, or one of its sub-projects. The location must belong to the zone and to your project or a sub-project; a parent project's location is refused.
## Behaviour
- `dryRun=true` returns `200` with `affected_devices` and `affected_residents` at the location, and a `warning` when either list is not empty. Nothing is written.
- Without `dryRun` (any other value counts as false), the location is deleted only when no device is assigned to it. The call answers `204` with no body.
- Reassign the location's devices before deleting it; the delete never unassigns devices for you.
- A location-deleted entry is added to the change history. A failure to write that entry does not fail the request.
## Example
```bash
curl -X DELETE 'https://api.sandbox.ovok.com/v1/slim/zones/c3499c27-2973-0a7f-807e-fb8676a92dcb/locations/87ea5dfc-8b8e-384d-8489-79496e706390?dryRun=true' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Update location in zone](/api/slim/zones/update-location-in-zone): Updates a location in a zone: its name, floor, room and bed, the devices assigned to it, and the resident on those devices.
**Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `Location:update` and `Device:update` (an admin or super admin who holds no access policy skips this check).
**Scope:** The zone must belong to the caller's project (from the token), its parent project, or one of its sub-projects. The location must be in the caller's project, and its managing organization must be the zone. Devices and residents named in the body must be in your project or a sub-project.
## Behaviour
- `name` is required and must be unique among the locations your token can search, not counting this one.
- `floor`, `room` and `bed` replace only the values you send; omitted ones keep their stored value. The resulting floor, room and bed must not match another location in the zone.
- `deviceId` (one UUID or a list) sets the devices at the location to exactly that set: devices not listed are removed from it, new ones are added. `null` removes every device. Omit `deviceId` to leave the devices unchanged.
- `patientId` assigns that resident to the devices named in `deviceId`, or, without `deviceId`, to the location's single device. A resident assigned there before is unassigned from those devices.
- **Omitting `patientId` unassigns the current resident from every device at the location** (and from the devices in `deviceId`). Send the current `patientId` to keep the resident.
- A discharged resident cannot be assigned.
- The write is atomic and takes a short write lock on the devices and locations it touches.
- The response is the updated location, in the same shape as `GET /v1/slim/location/{id}`.
## Example
```bash
curl -X PUT 'https://api.sandbox.ovok.com/v1/slim/zones/c3499c27-2973-0a7f-807e-fb8676a92dcb/locations/87ea5dfc-8b8e-384d-8489-79496e706390' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{
"name": "Room 101 Bed 1",
"room": "101",
"bed": "1",
"deviceId": "c3499c27-2973-0a7f-807e-fb8676a92dcb",
"patientId": "87ea5dfc-8b8e-384d-8489-79496e706390"
}'
```
- Sub Project
- [Check child project access policy drift](/api/sub-project/check-child-project-access-policy-drift): Compares the AccessPolicies of each direct child of your current project with your project's own, and reports the differences. Nothing is written.
**Auth:** bearer token of a project admin (`ProjectMembership.admin`).
**Scope:** the caller's project (from the token) and its direct children. Grandchildren are not compared.
## Behaviour
- Policies are matched by `name`, and compared on `compartment`, `resource` and `ipAccessRule`.
- Each difference is one `drift` entry with a `kind`:
- `missing`: the child has no policy with that name.
- `changed`: the child's policy differs from yours.
- `orphan`: the child holds a policy whose name your project does not have. A child's own policies, such as its patient-sharing template, show up as orphans.
- `copyOfDeletedPolicy` is true for an `orphan` that is a copy of one of your policies you have since deleted: every copy is tagged with the id of the policy it copies, and that policy is no longer in your project. It is false for every other entry, a child's own policies included, and always false for the two patient-sharing templates every project makes for itself, whatever their tag.
- `count` is the number of entries.
- `POST access-policy/reconcile` repairs the `missing` and `changed` entries and deletes the `copyOfDeletedPolicy` ones.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/internal/sub-project/access-policy/drift' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Check child project access policy drift](/api/sub-project/check-child-project-access-policy-drift-2): Compares the AccessPolicies of each direct child of your current project with your project's own, and reports the differences. Nothing is written.
**Auth:** bearer token of a project admin (`ProjectMembership.admin`).
**Scope:** the caller's project (from the token) and its direct children. Grandchildren are not compared.
## Behaviour
- Policies are matched by `name`, and compared on `compartment`, `resource` and `ipAccessRule`.
- Each difference is one `drift` entry with a `kind`:
- `missing`: the child has no policy with that name.
- `changed`: the child's policy differs from yours.
- `orphan`: the child holds a policy whose name your project does not have. A child's own policies, such as its patient-sharing template, show up as orphans.
- `copyOfDeletedPolicy` is true for an `orphan` that is a copy of one of your policies you have since deleted: every copy is tagged with the id of the policy it copies, and that policy is no longer in your project. It is false for every other entry, a child's own policies included, and always false for the two patient-sharing templates every project makes for itself, whatever their tag.
- `count` is the number of entries.
- `POST access-policy/reconcile` repairs the `missing` and `changed` entries and deletes the `copyOfDeletedPolicy` ones.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/internal/sub-project/access-policy/drift' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Check child project access policy drift](/api/sub-project/check-child-project-access-policy-drift-3): Compares the AccessPolicies of each direct child of your current project with your project's own, and reports the differences. Nothing is written.
**Auth:** bearer token of a project admin (`ProjectMembership.admin`).
**Scope:** the caller's project (from the token) and its direct children. Grandchildren are not compared.
## Behaviour
- Policies are matched by `name`, and compared on `compartment`, `resource` and `ipAccessRule`.
- Each difference is one `drift` entry with a `kind`:
- `missing`: the child has no policy with that name.
- `changed`: the child's policy differs from yours.
- `orphan`: the child holds a policy whose name your project does not have. A child's own policies, such as its patient-sharing template, show up as orphans.
- `copyOfDeletedPolicy` is true for an `orphan` that is a copy of one of your policies you have since deleted: every copy is tagged with the id of the policy it copies, and that policy is no longer in your project. It is false for every other entry, a child's own policies included, and always false for the two patient-sharing templates every project makes for itself, whatever their tag.
- `count` is the number of entries.
- `POST access-policy/reconcile` repairs the `missing` and `changed` entries and deletes the `copyOfDeletedPolicy` ones.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/internal/sub-project/access-policy/drift' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Check child project access policy drift](/api/sub-project/check-child-project-access-policy-drift-4): Compares the AccessPolicies of each direct child of your current project with your project's own, and reports the differences. Nothing is written.
**Auth:** bearer token of a project admin (`ProjectMembership.admin`).
**Scope:** the caller's project (from the token) and its direct children. Grandchildren are not compared.
## Behaviour
- Policies are matched by `name`, and compared on `compartment`, `resource` and `ipAccessRule`.
- Each difference is one `drift` entry with a `kind`:
- `missing`: the child has no policy with that name.
- `changed`: the child's policy differs from yours.
- `orphan`: the child holds a policy whose name your project does not have. A child's own policies, such as its patient-sharing template, show up as orphans.
- `copyOfDeletedPolicy` is true for an `orphan` that is a copy of one of your policies you have since deleted: every copy is tagged with the id of the policy it copies, and that policy is no longer in your project. It is false for every other entry, a child's own policies included, and always false for the two patient-sharing templates every project makes for itself, whatever their tag.
- `count` is the number of entries.
- `POST access-policy/reconcile` repairs the `missing` and `changed` entries and deletes the `copyOfDeletedPolicy` ones.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/internal/sub-project/access-policy/drift' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Repair child project access policy drift](/api/sub-project/repair-child-project-access-policy-drift): Repairs AccessPolicy drift in the direct children of your current project: it creates each missing copy and overwrites each changed one from your project's policy.
**Auth:** bearer token of a project admin (`ProjectMembership.admin`).
**Scope:** the caller's project (from the token) and its direct children. Grandchildren are not touched.
## Behaviour
- Uses the same comparison as `GET access-policy/drift` and returns what it found, in the same shape, before the repair.
- A `missing` policy is created in the child; a `changed` one is overwritten with your `compartment`, `resource` and `ipAccessRule`. Either way the child's copy is tagged with the id of your policy.
- An `orphan` with `copyOfDeletedPolicy: true` is deleted: it is a copy of a policy you have deleted. Members of the child who still hold it are locked out of the child until they are given another role.
- Any other `orphan` is listed and never deleted. A child's own policies carry no copy tag, and its patient-sharing templates are never deleted, whatever their tag.
- Idempotent: a second call finds nothing left to repair, apart from the orphans it keeps.
- The writes are not atomic. If one fails, the request fails; policies already written stay written, and you can call the route again.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/internal/sub-project/access-policy/reconcile' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Repair child project access policy drift](/api/sub-project/repair-child-project-access-policy-drift-2): Repairs AccessPolicy drift in the direct children of your current project: it creates each missing copy and overwrites each changed one from your project's policy.
**Auth:** bearer token of a project admin (`ProjectMembership.admin`).
**Scope:** the caller's project (from the token) and its direct children. Grandchildren are not touched.
## Behaviour
- Uses the same comparison as `GET access-policy/drift` and returns what it found, in the same shape, before the repair.
- A `missing` policy is created in the child; a `changed` one is overwritten with your `compartment`, `resource` and `ipAccessRule`. Either way the child's copy is tagged with the id of your policy.
- An `orphan` with `copyOfDeletedPolicy: true` is deleted: it is a copy of a policy you have deleted. Members of the child who still hold it are locked out of the child until they are given another role.
- Any other `orphan` is listed and never deleted. A child's own policies carry no copy tag, and its patient-sharing templates are never deleted, whatever their tag.
- Idempotent: a second call finds nothing left to repair, apart from the orphans it keeps.
- The writes are not atomic. If one fails, the request fails; policies already written stay written, and you can call the route again.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/internal/sub-project/access-policy/reconcile' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Repair child project access policy drift](/api/sub-project/repair-child-project-access-policy-drift-3): Repairs AccessPolicy drift in the direct children of your current project: it creates each missing copy and overwrites each changed one from your project's policy.
**Auth:** bearer token of a project admin (`ProjectMembership.admin`).
**Scope:** the caller's project (from the token) and its direct children. Grandchildren are not touched.
## Behaviour
- Uses the same comparison as `GET access-policy/drift` and returns what it found, in the same shape, before the repair.
- A `missing` policy is created in the child; a `changed` one is overwritten with your `compartment`, `resource` and `ipAccessRule`. Either way the child's copy is tagged with the id of your policy.
- An `orphan` with `copyOfDeletedPolicy: true` is deleted: it is a copy of a policy you have deleted. Members of the child who still hold it are locked out of the child until they are given another role.
- Any other `orphan` is listed and never deleted. A child's own policies carry no copy tag, and its patient-sharing templates are never deleted, whatever their tag.
- Idempotent: a second call finds nothing left to repair, apart from the orphans it keeps.
- The writes are not atomic. If one fails, the request fails; policies already written stay written, and you can call the route again.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/internal/sub-project/access-policy/reconcile' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Repair child project access policy drift](/api/sub-project/repair-child-project-access-policy-drift-4): Repairs AccessPolicy drift in the direct children of your current project: it creates each missing copy and overwrites each changed one from your project's policy.
**Auth:** bearer token of a project admin (`ProjectMembership.admin`).
**Scope:** the caller's project (from the token) and its direct children. Grandchildren are not touched.
## Behaviour
- Uses the same comparison as `GET access-policy/drift` and returns what it found, in the same shape, before the repair.
- A `missing` policy is created in the child; a `changed` one is overwritten with your `compartment`, `resource` and `ipAccessRule`. Either way the child's copy is tagged with the id of your policy.
- An `orphan` with `copyOfDeletedPolicy: true` is deleted: it is a copy of a policy you have deleted. Members of the child who still hold it are locked out of the child until they are given another role.
- Any other `orphan` is listed and never deleted. A child's own policies carry no copy tag, and its patient-sharing templates are never deleted, whatever their tag.
- Idempotent: a second call finds nothing left to repair, apart from the orphans it keeps.
- The writes are not atomic. If one fails, the request fails; policies already written stay written, and you can call the route again.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/internal/sub-project/access-policy/reconcile' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [List project relationships](/api/sub-project/list-project-relationships): Returns where your current project sits in the project hierarchy: its parent and its direct children.
**Auth:** bearer token of a project admin (`ProjectMembership.admin`).
**Scope:** the caller's project (from the token).
## Behaviour
- `parentProjectId` is `null` for a project with no parent.
- `subProjectIds` lists direct children only.
- The result is cached for up to 5 minutes. Creating or deleting a child through Ovok clears the cache; other changes can take that long to show.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/internal/sub-project' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Create a sub tenant](/api/sub-project/create-a-sub-tenant): Creates a child project (sub-tenant) of your current project. Use it from admin tooling and from headless integrations that hold an admin client token.
**Auth:** bearer token of a project admin (`ProjectMembership.admin`). Setting `parentProjectId` also requires a super admin.
**Scope:** the parent is the caller's project (from the token), or `parentProjectId` for a super admin.
## Behaviour
- Creates, all or nothing: the `Project` (with the standard Ovok project features, `extension` copied as given, and its own Signals tenant), its tenant code, the parent's link to it, a Practitioner and an admin membership in the new project, and the parent-child relationship. If a step fails, the steps already done are undone.
- **User session:** the new project is owned by your user, the Practitioner copies your profile, and the membership is yours. The response is `{ "success": true }`.
- **Client application session** (no user): the new project is owned by the parent project. The response is `{ "success": true, "projectId": "..." }`.
- A concurrent create of the same name under the same parent is refused with 409 and writes nothing. A later create with the same name makes a second project.
- Served at both `/v1/internal/sub-project` and `/sub-project`.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/internal/sub-project' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{ "name": "My Medical Project" }'
```
- [List project relationships](/api/sub-project/list-project-relationships-2): Returns where your current project sits in the project hierarchy: its parent and its direct children.
**Auth:** bearer token of a project admin (`ProjectMembership.admin`).
**Scope:** the caller's project (from the token).
## Behaviour
- `parentProjectId` is `null` for a project with no parent.
- `subProjectIds` lists direct children only.
- The result is cached for up to 5 minutes. Creating or deleting a child through Ovok clears the cache; other changes can take that long to show.
## Example
```bash
curl -X GET 'https://api.sandbox.ovok.com/v1/internal/sub-project' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
```
- [Create a sub tenant](/api/sub-project/create-a-sub-tenant-2): Creates a child project (sub-tenant) of your current project. Use it from admin tooling and from headless integrations that hold an admin client token.
**Auth:** bearer token of a project admin (`ProjectMembership.admin`). Setting `parentProjectId` also requires a super admin.
**Scope:** the parent is the caller's project (from the token), or `parentProjectId` for a super admin.
## Behaviour
- Creates, all or nothing: the `Project` (with the standard Ovok project features, `extension` copied as given, and its own Signals tenant), its tenant code, the parent's link to it, a Practitioner and an admin membership in the new project, and the parent-child relationship. If a step fails, the steps already done are undone.
- **User session:** the new project is owned by your user, the Practitioner copies your profile, and the membership is yours. The response is `{ "success": true }`.
- **Client application session** (no user): the new project is owned by the parent project. The response is `{ "success": true, "projectId": "..." }`.
- A concurrent create of the same name under the same parent is refused with 409 and writes nothing. A later create with the same name makes a second project.
- Served at both `/v1/internal/sub-project` and `/sub-project`.
## Example
```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/internal/sub-project' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \
-H 'Content-Type: application/json' \
-d '{ "name": "My Medical Project" }'
```
- [List project relationships](/api/sub-project/list-project-relationships-3): Returns where your current project sits in the project hierarchy: its parent and its direct children.
**Auth:** bearer token of a project admin (`ProjectMembership.admin`).
**Scope:** the caller's project (from the token).
## Behaviour
- `parentProjectId` is `null` for a project with no parent.
- `sub