----- BEGIN PAGE https://docs.canvasmedical.com/guides/appointments-additional_fields/
This guide explains how to add additional fields that will appear when scheduling an appointment. With this, you can define custom fields, and the information will be stored as appointments metadata.
##  What you'll learn: 
  - Use the [`AppointmentMetadataCreateForm`](/sdk/appointment-metadata-create-form-effect) effect to display additional fields when scheduling an appointment.
  - Use the [`FormField`](/sdk/appointment-metadata-create-form-effect/#formfield) class to create fields
##  Appointment Metadata Create form plugin 
####  1\. FormField 
To create the form, we need to specify which items will be included. For this, we use the [`FormField`](/sdk/appointment-metadata-create-form-effect/#formfield) class, where we can define our inputs and their attributes.
    ```python
    from canvas_sdk.effects.appointments_metadata import FormField, InputType
    FormField(
        key='state',
        label='State',
        type=InputType.TEXT,
        required=False,
        editable=True,
        value="CA"
    ),
    ```
####  2\. AppointmentMetadataCreateFormEffect 
The next step is to add these fields to the effect so they can be used to build the form.
    ```python
    from canvas_sdk.effects.appointments_metadata import AppointmentsMetadataCreateFormEffect, FormField, InputType
    AppointmentsMetadataCreateFormEffect(form_fields=[
        FormField(
            key='state',
            label='State',
            type=InputType.TEXT,
            required=False,
            editable=True,
            value="CA"
        ),
        ...,
    ])
    ```
####  3\. The plugin 
Here's an example of a complete plugin showcasing the different input types.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.appointments_metadata import AppointmentsMetadataCreateFormEffect, InputType, FormField
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    # Inherit from BaseHandler to properly get registered for events
    class Handler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.APPOINTMENT__FORM__GET_ADDITIONAL_FIELDS)
        def compute(self) -> list[Effect]:
            form = AppointmentsMetadataCreateFormEffect(form_fields=[
                FormField(
                    key='state',
                    label='State',
                    type=InputType.TEXT,
                    required=False,
                    editable=True,
                ),
                FormField(
                    key='occupation',
                    label='Occupation',
                    type=InputType.SELECT,
                    required=False,
                    editable=True,
                    options=["Engineer", "Teacher", "Other"]
                )
            ])
            return [form.apply()]
    ```
####  4\. The Output 
Below, you can see how it will appear in the app — these fields will be stored as appointments metadata.
![appointments-additional-fields](/assets/images/appointments-additional-fields.png)
----- END PAGE https://docs.canvasmedical.com/guides/appointments-additional_fields/


----- BEGIN PAGE https://docs.canvasmedical.com/guides/creating-webhooks-with-the-canvas-sdk/
Webhooks are user-defined callbacks that make a request to an API. You may want to create a webhook to notify a system you control that some event has occurred in Canvas. This guide shows how to create a webhook that sends an API request containing the ID of a Task upon its creation.
> **Info:** This guide assumes pre-existing knowledge of the Canvas SDK. If you're starting from scratch, you may want to read and implement [Your First Plugin (with Claude Code)](/guides/your-first-plugin-with-claude-code/) before working through this exercise. 
##  Initialize a new plugin 
The Canvas CLI gives you a great head start when creating a plugin. Simply run `canvas init`, and answer the prompt to name your plugin.
    ```sh
    $ canvas init
      [1/1] project_name (My Cool Plugin): Task Webhook
    Project created in /Users/andrew/src/canvas-plugins/task-webhook
    ```
This output shows the location of our freshly generated plugin.
##  Edit the plugin code 
The default content of this file shows you the information you have available to you in the comments. I've stripped it down to almost nothing so we can layer in the functionality step by step.
###  Log a message when a task is created. 
The code below listens for the `TASK_CREATED` event and logs the string "A Task was created!".
    ```python
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from logger import log
    class Handler(BaseHandler):
        """
        When a task is created, log a message
        """
        RESPONDS_TO = EventType.Name(EventType.TASK_CREATED)
        def compute(self):
            """
            Log a message as tasks are created.
            """
            log.info("A Task was created!")
            return []
    ```
You can see this log output by first streaming logs with `canvas logs` and then creating a task. You can create this task with Canvas Chat, a Task Command, or our [FHIR Task Create endpoint](/api/task/#create).
After you've [installed your plugin](/sdk/canvas_cli/#canvas-install) and created a task, you should see this in your log stream:
    ```sh
    INFO 2024-09-26 17:04:08,396 Starting server, listening on port 50051
    INFO 2024-09-26 17:04:08,396 Loading custom-plugins/task_webhook
    INFO 2024-09-26 17:04:08,396 Loading plugin 'task_webhook:task_webhook.handlers.event_handlers:Handler'
    INFO 2024-09-26 17:04:24,410 A Task was created!
    INFO 2024-09-26 17:04:24,410 task_webhook:task_webhook.handlers.event_handlers:Handler.compute() completed (0 ms)
    INFO 2024-09-26 17:04:24,411 Responded to Event TASK_CREATED (1 ms)
    ```
Awesome! But we're not here to log, we need to make an API request. To do that, we need to use the HTTP client found in the Canvas SDK's [Utils module](/sdk/utils/)
###  Make an HTTP request when a Task is created 
We can use <https://webhook.site/> for a quick way to test our webhook and see the requests it receives. Here is the updated code that uses the HTTP client to make the request:
    ```python
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.utils import Http
    from logger import log
    class Handler(BaseHandler):
        """
        When a task is created, hit a webhook
        """
        RESPONDS_TO = EventType.Name(EventType.TASK_CREATED)
        def compute(self):
            """
            Notify our server of tasks as they are created.
            """
            url = "https://webhook.site/ee7aed78-b652-4d9e-b858-04465c409d15"
            payload = {
                "message": "A Task was created!"
            }
            http = Http()
            response = http.post(url, json=payload)
            if response.ok:
                log.info("Successfully notified API of task creation!")
            else:
                log.info("Notification unsuccessful. =[")
            return []
    ```
After you've [installed your updated plugin](/sdk/canvas_cli/#canvas-install) and created a task, you should see this in your log stream:
    ```sh
    INFO 2024-09-26 17:18:23,206 Loading custom-plugins/task_webhook
    INFO 2024-09-26 17:18:23,207 Reloading plugin 'task_webhook:task_webhook.handlers.event_handlers:Handler'
    INFO 2024-09-26 17:18:33,850 Successfully notified API of task creation!
    INFO 2024-09-26 17:18:33,851 task_webhook:task_webhook.handlers.event_handlers:Handler.compute() completed (693 ms)
    INFO 2024-09-26 17:18:33,851 Responded to Event TASK_CREATED (696 ms)
    ```
This log output is a great reminder for me to mention that making HTTP requests to external servers will slow plugin execution while it waits on the external server to respond. It's a good idea to make sure the servers you're hitting have a sufficiently quick response time.
Checking in on our webhook.site logs shows it received our request! ![Log of web
request](/assets/images/webhook-guide/webhook-guide-first-request.png)
Awesome, but you aren't sending these requests to a server that allows unauthenticated requests! And this request doesn't even tell you anything about the task it's notifying you about. This isn't useful at all!
Let's do another iteration, this time using the information provided along with the event so that we can send a usable message, securely. We're specifically going to incorporate the event's `target` and `secrets`.
###  Make an authenticated HTTP request that includes the newly created Task's ID 
Within your `Handler` class, you have access to `self.target`, which represents the ID of the subject of the event. In our case, it will be the Task's ID. This is the same ID used in the [FHIR Task endpoints](/api/task/), so you can use it to make FHIR API requests.
You also have access to `self.secrets`, which is a python dictionary containing the key-value pairs from your plugins configuration page. You declare the keys in your `CANVAS_MANIFEST.json`, and can then set the values after the plugin is installed.
We'll set two secrets, one for the unique id of the webhook, and one for an auth token. Here's what the manifest file looks like with secrets declared:
    ```json
    {
        "sdk_version": "0.1.4",
        "plugin_version": "0.0.1",
        "name": "task_webhook",
        "description": "Webhooks for task creation",
        "components": {
            "handlers": [
                {
                    "class": "task_webhook.handlers.event_handlers:Handler",
                    "description": "Hit an API when a task is created",
                    "data_access": {
                        "event": "",
                        "read": [],
                        "write": []
                    }
                }
           ]
        },
        "variables": [
            {"name": "WEBHOOK_ID", "sensitive": true},
            {"name": "AUTH_TOKEN", "sensitive": true}
        ],
        "tags": {},
        "license": "",
        "readme": "./README.md"
    }
    ```
The `variables` array declares two sensitive variables, `WEBHOOK_ID` and `AUTH_TOKEN`. After we update the plugin, we can set values for these in the plugin configuration page. This allows for different values to be used across different installations. Marking each entry with `"sensitive": true` means the values will be masked in the Admin UI and listed only as `[set]` or `[not set]` by `canvas config list`.
Here's how that configuration looks:
![Plugin secrets
configuration](/assets/images/webhook-guide/webhook-guide-secrets.png)
With those values set, we can use them in our code:
    ```python
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.utils import Http
    from logger import log
    class Handler(BaseHandler):
        """
        When a task is created, hit a webhook
        """
        RESPONDS_TO = EventType.Name(EventType.TASK_CREATED)
        def compute(self):
            """
            Notify our server of tasks as they are created.
            """
            url = f"https://webhook.site/{self.secrets['WEBHOOK_ID']}"
            headers = {
                "Authorization": f"Bearer {self.secrets['AUTH_TOKEN']}"
            }
            payload = {
                "message": "A Task was created!",
                "resource_id": self.target
            }
            http = Http()
            response = http.post(url, json=payload, headers=headers)
            if response.ok:
                log.info("Successfully notified API of task creation!")
            else:
                log.info("Notification unsuccessful. =[")
            return []
    ```
And checking once more on our webhook.site logs shows it received our updated request, including our `AUTH_TOKEN` value and the created task's ID.
![Log of web
request](/assets/images/webhook-guide/webhook-guide-second-request.png)
##  Listening for multiple events 
A single plugin handler can listen for multiple event types. The event type will be available in `self.event.type`, which will contain a member of the `EventType` enum. The [full list of events is available](/sdk/events/#event-types-and-context). Here is a short example that listens for two different events:
    ```python
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.utils import Http
    from logger import log
    class Handler(BaseHandler):
        """
        When a task is created or updated, hit a webhook
        """
        RESPONDS_TO = [
            EventType.Name(EventType.TASK_CREATED),
            EventType.Name(EventType.TASK_UPDATED),
        ]
        def compute(self):
            """
            Notify our server of tasks as they are created.
            """
            url = f"https://webhook.site/{self.secrets['WEBHOOK_ID']}"
            headers = {"Authorization": f"Bearer {self.secrets['AUTH_TOKEN']}"}
            # self.event.type is a member of the EventType enum corresponding to
            # one of the event types in the handler's RESPONDS_TO attribute
            verb = 'created' if self.event.type == EventType.TASK_CREATED else 'updated'
            payload = {
                "message": f"A Task was {verb}!",
                "resource_id": self.target,
            }
            http = Http()
            response = http.post(url, json=payload, headers=headers)
            # You can also get the name of the event as as string using EventType.Name()
            event_name = EventType.Name(self.event.type)
            if response.ok:
                log.info(f"Successfully notified API of {event_name}")
            else:
                log.info(f"Notification of {event_name} unsuccessful. =[")
            return []
    ```
Alternatively, you could include several classes, each resposible for some specific request type. When including several classes in one plugin, they all have access to the same secrets dictionary, you just need to declare each class in the manifest file.
##  Conclusion 
I hope you found this helpful. Happy coding, we can't wait to see what you build!
----- END PAGE https://docs.canvasmedical.com/guides/creating-webhooks-with-the-canvas-sdk/


----- BEGIN PAGE https://docs.canvasmedical.com/guides/custom-landing-page/
This guide provides examples of how to leverage patient data to personalize the portal landing page, and explains how to integrate the ready-made widgets provided by Canvas.
> **Info:** This guide assumes pre-existing knowledge of the Canvas SDK. If you're starting from scratch, you may want to read and implement [Your First Plugin (with Claude Code)](/guides/your-first-plugin-with-claude-code/) before working through this exercise. 
##  What Are Widgets in the Patient Portal? 
[Widgets](/sdk/patient-portal/#portal-landing-page-widgets) in the patient portal are interactive components that enhance the user experience by providing quick access to information and functionalities. They can display key details like upcoming appointments. Widgets can be fully customized with unique content or leverage ready-made components—such as Appointments and Messaging provided by Canvas to ensure consistency and ease of use. These widgets are organized on the landing page using a grid layout, which supports various sizes to optimize the visual presentation and responsiveness across different devices.
##  How to add a Widget? 
A widget can be added by listening to the `PATIENT_PORTAL__WIDGET_CONFIGURATION` event and returning one or several `PortalWidget`
###  Step 1: Initialize a plugin 
The Canvas CLI gives you a great head start when creating a plugin. Simply run
    ```bash
      canvas init
    ```
Then, follow the prompts to name and configure your new plugin project.
###  Step 2: Update your handler 
Modify your handler to handle the widget configuration event. For example:
    ```python
    from canvas_sdk.effects.widgets import PortalWidget
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class Handler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.PATIENT_PORTAL__WIDGET_CONFIGURATION)
        def compute(self):
            widget = PortalWidget(
              content="Hello World",
              size=PortalWidget.Size.COMPACT,
              priority=10
            )
            return [widget.apply()]
    ```
This code listens for the `PATIENT_PORTAL__WIDGET_CONFIGURATION` event and, when triggered, creates a new widget with a simple "Hello World" message, a compact size, and a priority of 10.
##  Patient medication widget 
This widget will show the last medication and CTA to request a refill.
So let's update the example above to:
  - Fetch the patient's medication.
  - Leverage [HTML templating](/sdk/layout-effect/#custom-html-and-django-templates) to display the necessary information
###  Step 1: Fetch patient medication 
Since the event includes the patient object, you can easily access all the necessary data. Add the following snippet to your compute method to retrieve the patient's details:
    ```python
    patient = Patient.objects.get(id=self.target)
    last_medication = patient.medications.last()
    ```
###  Step 2: Prepare HTML template 
Create a `templates` folder inside your plugin's folder:
    ```bash
      mkdir templates
    ```
Add the HTML file:
    ```bash
      touch medication_widget.html
    ```
And add the following HTML:
    ```html
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <style>
        body, html {
          height: 100%;
          width: 100%;
          margin: 0;
          font-family: "Roboto","Helvetica","Arial",sans-serif;
          font-size: 16px;
        }
      </style>
    </head>
    <body>
    </body>
    </html>
    ```
###  Step 3: Design your widget 
> **Info:** Developers are responsible for providing all the necessary CSS to style their widget. The widget does not include default styles, so you should include custom CSS—either inline or via an external stylesheet—to define the layout, typography, colors, spacing, and interactive elements. 
While you can design your widget in any way that suits your needs, for this example we'll create one featuring a header and a card component. The card will display the medication name, the prescription date, and a clear call-to-action (CTA) button that redirects the patient to the messaging page to request a refill.
####  Header 
A light gray background color and a blue text to ensure it matches the patient portal aesthetic.
    ```html
    <style>
      .header {
          padding: 8px;
          background: #E5E5E5;
          color: #2185D0;
          text-align: left;
          margin: 0;
          font-weight: 500;
          font-size: 18px;
          line-height: 1.6;
        }
    </style>
    <body>
        <div class="header">My Health</div>
    </body>
    ```
####  Card Component 
We will add template variables for the medication name and start date, allowing these values to be dynamically updated in the plugin.
    ```html
    <body>
      <div class="widget">
        <div class="medication-info">
          <span class="material-icons">medication</span>
          <span>{{name}}</span>
        </div>
        <p style="padding: 0 12px">This medication was prescribed on {{start_date}}. Do you need a refill?</p>
        <button onclick="onClick()">Ask for a refill</button>
      </div>
      <script>
        function onClick() {
          window.top.location.href = "http://localhost:8000/app/messaging"
        }
      </script>
    </body>
    ```
Let's dive in and add some styles to the card.
Our `widget` class ensures that all elements are both vertically and horizontally centered, creating a sleek card design with rounded borders and a subtle shadow for a modern, elevated look.
    ```css
    .widget {
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      width: 98%;
      height: 80%;
      border-radius: 4px;
      box-shadow: 0 2px 1px -1px rgba(0, 0, 0, 0.2), 0 1px 1px 0 rgba(0, 0, 0, 0.14), 0 1px 3px 0 rgba(0, 0, 0, 0.12);
      background-color: #f9f9f9;
      text-align: center;
      margin: 4px auto auto;
    }
    ```
Our medication info section will feature a Material UI icon next to the medication name, providing a clear and modern visual representation consistent with the portal's design aesthetic.
    ```css
    .medication-info {
      display: flex;
      align-items: center;
      gap: 10px;
      margin-bottom: 15px;
      padding: 0 8px;
      font-size: 14px;
    }
    .material-icons {
      font-size: 50px;
    }
    ```
And finally, we style the CTA button to mimic a Material UI button, ensuring it aligns perfectly with the portal's overall design aesthetic.
    ```css
    button {
      margin-top: 16px;
      background-color: #1976d2;
      color: #fff;
      border: none;
      border-radius: 4px;
      padding: 16px 16px;
      font-size: 14px;
      min-width: 64px;
      text-transform: uppercase;
      box-shadow: 0 3px 1px -2px rgba(0,0,0,0.2),
                  0 2px 2px 0 rgba(0,0,0,0.14),
                  0 1px 5px 0 rgba(0,0,0,0.12);
      cursor: pointer;
      transition: background-color 0.3s ease;
    }
    button:hover {
      background-color: #115293;
    }
    ```
###  Step 4: Tying everything together 
Update your plugin's `compute` method to pass the desired values for medication name and start date using `render_to_string` function
    ```python
    medication_info = {
        "start_date": last_medication.start_date.strftime("%B %d, %Y"),
        "name": last_medication.codings.first().display
    }
    widget = PortalWidget(content=render_to_string("templates/medication_widget.html", medication_info), size=PortalWidget.Size.COMPACT, priority=10)
    ```
![medication widget](/assets/images/sdk/widgets/patient_medication_widget.png)
###  Full Example 
    ```python
    from canvas_sdk.effects.widgets import PortalWidget
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.templates import render_to_string
    from canvas_sdk.v1.data import Patient
    class Handler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.PATIENT_PORTAL__WIDGET_CONFIGURATION)
        def compute(self):
            patient = Patient.objects.get(id=self.target)
            last_medication = patient.medications.last()
            medication_info = {
                "start_date": last_medication.start_date.strftime("%B %d, %Y"),
                "name": last_medication.codings.first().display
            }
            medication_widget = PortalWidget(
              content=render_to_string("templates/medication_widget.html", medication_info),
              size=PortalWidget.Size.COMPACT,
              priority=10
            )
            return [medication_widget.apply()]
    ```
    ```html
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
      <style>
        body, html {
          height: 100%;
          width: 100%;
          margin: 0;
          font-family: "Roboto","Helvetica","Arial",sans-serif;
          font-size: 16px;
        }
        .widget {
          display: flex;
          flex-direction: column;
          justify-content: center;
          align-items: center;
          width: 98%;
          height: 80%;
          border-radius: 4px;
          box-shadow: 0 2px 1px -1px rgba(0, 0, 0, 0.2), 0 1px 1px 0 rgba(0, 0, 0, 0.14), 0 1px 3px 0 rgba(0, 0, 0, 0.12);
          background-color: #f9f9f9;
          text-align: center;
          margin: 4px auto auto;
        }
        .header {
          padding: 8px;
          background: #E5E5E5;
          color: #2185D0;
          text-align: left;
          margin: 0;
          font-weight: 500;
          font-size: 18px;
          line-height: 1.6;
        }
        .medication-info {
          display: flex;
          align-items: center;
          gap: 10px;
          margin-bottom: 15px;
          padding: 0 8px;
          font-size: 14px;
        }
        .material-icons {
          font-size: 50px;
        }
        button {
          margin-top: 16px;
          background-color: #1976d2;
          color: #fff;
          border: none;
          border-radius: 4px;
          padding: 16px 16px;
          font-size: 14px;
          min-width: 64px;
          text-transform: uppercase;
          box-shadow: 0 3px 1px -2px rgba(0,0,0,0.2),
                      0 2px 2px 0 rgba(0,0,0,0.14),
                      0 1px 5px 0 rgba(0,0,0,0.12);
          cursor: pointer;
          transition: background-color 0.3s ease;
        }
        button:hover {
          background-color: #115293;
        }
      </style>
    </head>
    <body>
      <div class="header">My Health</div>
      <div class="widget">
        <div class="medication-info">
          <span class="material-icons">medication</span>
          <span>{{name}}</span>
        </div>
        <p style="padding: 0 12px">This medication was prescribed on {{start_date}}. Do you need a refill?</p>
        <button onclick="onClick()">Ask for a refill</button>
      </div>
      <script>
        function onClick() {
          window.top.location.href = "http://localhost:8000/app/messaging"
        }
      </script>
    </body>
    </html>
    ```
##  Upcoming appointments Widget provided by Canvas 
This is one of the ready-made widgets provided by Canvas that you can add to your patient portal. It will show upcoming appointments.
###  Step 1: Add a new handler to your plugin 
Create a new handler in your plugin with the following content:
    ```python
    from canvas_sdk.effects.widgets import PortalWidget
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class UpcomingAppointmentWidget(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.PATIENT_PORTAL__WIDGET_CONFIGURATION)
        def compute(self):
            widget = PortalWidget(component=PortalWidget.Component.APPOINTMENTS, priority=25)
            return [widget.apply()]
    ```
![medication widget](/assets/images/sdk/widgets/upcoming_appointments_widget.png)
----- END PAGE https://docs.canvasmedical.com/guides/custom-landing-page/


----- BEGIN PAGE https://docs.canvasmedical.com/guides/customize-panel-buttons/
This guide is intended to help you customize which buttons are shown on the main page and the patient page. It allows you to add, order, or hide specific buttons as needed.
##  Getting Started 
To achieve this, you'll use the [PANEL_SECTIONS_CONFIGURATION](/sdk/events) event, which sends both the patient and the user attributes.
If you need to identify where the event is coming from, you can check if the patient attributes are present — on the global (main) page, the patient will be empty.
##  Defining the Sections 
In the SDK, you'll use the [PanelConfiguration](/sdk/layout-effect/#panel-configuration) effect. This effect takes in the sections you want to display and specifies the page where they should appear.
You'll use:
  - PanelPatientSection – for sections shown on the patient page
  - PanelGlobalSection – for sections shown on the main (global) page
The SDK has built-in safeguards to prevent you from mixing them up or placing a section on the wrong page.
That's It!
Once you apply the effect, your panel is configured — go check your page to see the changes in action!
##  The Complete Example 
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.panel_configuration import PanelConfiguration
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class CustomizePatientSection(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.PANEL_SECTIONS_CONFIGURATION)
        def compute(self) -> list[Effect]:
            patient = self.target
            # user = self.context["user"]
            if patient:
                return [PanelConfiguration(sections=[
                    PanelConfiguration.PanelPatientSection.REFILL_REQUEST,
                    PanelConfiguration.PanelPatientSection.LAB_REPORT,
                    PanelConfiguration.PanelPatientSection.CHANGE_REQUEST,
                    PanelConfiguration.PanelPatientSection.TASK,
                ], page=PanelConfiguration.Page.PATIENT if patient else PanelConfiguration.Page.GLOBAL).apply()]
            else:
                return []
    class CustomizeGlobalSection(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.PANEL_SECTIONS_CONFIGURATION)
        def compute(self) -> list[Effect]:
            patient = self.target
            # user = self.context["user"]
            if not patient:
                return [PanelConfiguration(sections=[
                    PanelConfiguration.PanelGlobalSection.LAB_REPORT,
                    PanelConfiguration.PanelGlobalSection.TASK,
                    PanelConfiguration.PanelGlobalSection.CHANGE_REQUEST,
                ], page=PanelConfiguration.Page.PATIENT if patient else PanelConfiguration.Page.GLOBAL).apply()]
            else:
                return []
    ```
----- END PAGE https://docs.canvasmedical.com/guides/customize-panel-buttons/


----- BEGIN PAGE https://docs.canvasmedical.com/guides/customize-search-results/
In a typical visit note, it's common for clinicians to make 20, 30, even 50 or more selections from structured terminologies with commands like Diagnose, Prescribe, Family History, and many more. You can help clinicians make faster and more accurate selections with Canvas plugins. Write simple plugin code to apply custom filtering, sorting, and search result annotations in real time with near zero latency.
This search modification can help clinicians:
  - Choose the most appropriate medication that is also covered by insurance
  - Prioritize in-network specialists
  - Consider appropriate risk adjustment factors when selecting diagnosis codes
Canvas supports modifying search results in [all refactored commands](/product-updates/commands-module/#progress).
First, we'll show you a complete example of customizing the search results for choosing a medication in a Medication Statement command, then we'll break it down piece by piece so you can adapt the example to your own needs.
##  The Complete Example 
This example checks for the presence of a particular medication in the search results and, if present, annotates that medication option with additional information and adjusts its position to the top of the search results.
For reference, here's the difference in behavior with the plugin inactive vs active:
**Inactive (normal behavior):**
![With the plugin inactive, the results are unaltered](/assets/images/customize-search-results/plugin-inactive.png)
**Active (modified behavior):**
![With the plugin active, the preferred result is listed first, and with additional context](/assets/images/customize-search-results/plugin-active.png)
Here's the code in its entirety:
    ```python
    import json
    from canvas_sdk.events import EventType
    from canvas_sdk.effects import Effect, EffectType
    from canvas_sdk.handlers import BaseHandler
    class Handler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.MEDICATION_STATEMENT__MEDICATION__POST_SEARCH)
        def compute(self):
            results = self.context.get("results")
            if results is None:
                return [Effect(type=EffectType.AUTOCOMPLETE_SEARCH_RESULTS, payload=json.dumps(None))]
            post_processed_results = []
            for result in results:
                should_float_to_top = False
                for coding in result.get("extra", {}).get("coding", []):
                    if (
                        coding.get("code") == 554704
                        and coding.get("system") == "http://www.fdbhealth.com/"
                    ):
                        if result.get("annotations") is None:
                            result["annotations"] = []
                        result["annotations"].append("Kirkland Signature")
                        should_float_to_top = True
                if should_float_to_top:
                    post_processed_results.insert(0, result)
                else:
                    post_processed_results.append(result)
            return [
                Effect(
                    type=EffectType.AUTOCOMPLETE_SEARCH_RESULTS,
                    payload=json.dumps(post_processed_results),
                )
            ]
    ```
##  Anatomy of the Example 
This code can be broken down into the following sections:
  - Register interest in the correct search event
  - Decide whether to make any changes
  - Loop through the results, making modifications as appropriate
  - Return the modified results as a properly typed effect
###  Register interest in the correct search event 
    ```python
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class Handler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.MEDICATION_STATEMENT__MEDICATION__POST_SEARCH)
        def compute(self):
            results = self.context.get("results")
    ```
The class inherits from `BaseHandler`, which clues the plugin-runner into registering your code as interested in the event or events listed in the `RESPONDS_TO` class constant. We only specify one event here, `MEDICATION_STATEMENT__MEDICATION__POST_SEARCH`, but you could make this value a list to fire on multiple events. The event we've chosen to listen for can be read backwards to understand when it fires. This event is emitted after (" _post_ ") the normal _search_ results are found for the _medication_ autocomplete field of the _medication statement_ command. This event comes with a context that contains the search results that would be served to the user if there were no modifications.
###  Decide whether to make any changes 
    ```python
            if results is None:
                return [Effect(type=EffectType.AUTOCOMPLETE_SEARCH_RESULTS, payload=json.dumps(None))]
    ```
If the value of the results `is None`, we bail out early. There is a subtle difference between results of `None` and an empty result set (`[]`). Results being `None` means "make no changes, present the results without modification", whereas an empty result set means "present no options to the user".
###  Loop through the results, making modifications as appropriate 
    ```python
            post_processed_results = []
            for result in results:
                should_float_to_top = False
                for coding in result.get("extra", {}).get("coding", []):
                    if (
                        coding.get("code") == 554704
                        and coding.get("system") == "http://www.fdbhealth.com/"
                    ):
                        if result.get("annotations") is None:
                            result["annotations"] = []
                        result["annotations"].append("Kirkland Signature")
                        should_float_to_top = True
                if should_float_to_top:
                    post_processed_results.insert(0, result)
                else:
                    post_processed_results.append(result)
    ```
In this block of code, we create a new list named `post_processed_results` to hold our modified result set. We then loop through each result in the unmodified results set, and check to see if the current medication result matches our chosen criteria (FDB code 554704).
If it does match, we first check to see if any annotations already exist and initialize the annotations list if needed. We then append our chosen annotation to the result's annotation list and flag it as needing to be floated to the top (we had defaulted it to not be floated earlier on).
Finally, we add the result to our parallel list, `post_processed_results`. If it matched and was marked as being floated to the top, we insert it into the list at position 0. If it did not match, we append the result to the end of the list.
###  Return the modified results as a properly typed effect 
    ```python
            return [
                Effect(
                    type=EffectType.AUTOCOMPLETE_SEARCH_RESULTS,
                    payload=json.dumps(post_processed_results),
                )
            ]
    ```
With our list of modified results in place, we just need to return an effect of type `AUTOCOMPLETE_SEARCH_RESULTS` with our modified list as the payload.
The dropdown of options presented to the user now reflects our modifications!
##  Understanding Search Result Data Structures 
The search results in this example follow the MedicationSearchResult structure. Each result contains fields like `text`, `disabled`, `description`, `annotations`, `extra`, and `value` that provide detailed information about the medication option.
For complete details about medication search result data contracts and other search result structures, see the [Search Result Data Structures](/sdk/events/#search-result-data-structures) section in the Events documentation.
##  Offering your own providers alongside the directory 
The four provider-search surfaces — Refer, Imaging Order, fax recipient, and a patient's external care team — search the shared contact directory by default. Providers you create with the [ServiceProvider effect](/sdk/effect-service-provider/) are not searched automatically, so if you maintain your own directory you have to offer them yourself.
The pattern is the same on all four surfaces: query your own [ServiceProvider](/sdk/data-serviceprovider/) records, put them ahead of the directory's results, and return the combined list.
**Use the POST_SEARCH event, not PRE_SEARCH.** Only the post-search context carries what the directory returned, in `context["results"]`. On a pre-search that list is empty, so there is nothing to merge with — and on a command pre-search any `AUTOCOMPLETE_SEARCH_RESULTS` effect you return is authoritative, which means an empty reply blanks the dropdown instead of leaving it alone.
Which helper you call depends on the surface:
Surface | Event | Helper  
---|---|---  
Refer | `REFER__REFER_TO__POST_SEARCH` | `as_search_result()`  
Imaging Order | `IMAGING_ORDER__IMAGING_CENTER__POST_SEARCH` | `as_search_result()`  
Fax recipient | `FAX__RECIPIENT__POST_SEARCH` | `as_search_contact()`  
External care team | `PATIENT_PROFILE__EXTERNAL_CARE_TEAM__POST_SEARCH` | `as_search_contact()`  
    ```python
    import json
    from canvas_sdk.effects import Effect, EffectType
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.v1.data import ServiceProvider
    # Cap the query so a loose search term cannot pull your whole table into the sandbox.
    MAX_LOCAL_PROVIDERS = 200
    # The sandbox has no Q objects, so each field is queried separately and unioned in Python.
    SEARCHABLE_FIELDS = ("first_name", "last_name", "practice_name", "specialty")
    def matching_providers(search_term):
        matches = {}
        for field in SEARCHABLE_FIELDS:
            providers = ServiceProvider.objects.filter(
                is_active=True, **{f"{field}__icontains": search_term}
            )[:MAX_LOCAL_PROVIDERS]
            for provider in providers:
                matches.setdefault(provider.dbid, provider)
        return list(matches.values())[:MAX_LOCAL_PROVIDERS]
    class ContactDirectorySearch(BaseHandler):
        """Offer our own providers above the directory's on the fax and care team searches."""
        RESPONDS_TO = [
            EventType.Name(EventType.FAX__RECIPIENT__POST_SEARCH),
            EventType.Name(EventType.PATIENT_PROFILE__EXTERNAL_CARE_TEAM__POST_SEARCH),
        ]
        def compute(self):
            search_term = str(self.event.context.get("search_term") or "").strip()
            if not search_term:
                # An empty term must not push the entire local directory into the dropdown.
                return self.no_opinion()
            matches = matching_providers(search_term)
            if not matches:
                return self.no_opinion()
            ours = [
                provider.as_search_contact(["Our directory"])
                for provider in matches
            ]
            # Keep the directory's results underneath, minus anyone we already offered.
            superseded = {provider.full_name.lower() for provider in matches}
            theirs = [
                result
                for result in (self.event.context.get("results") or [])
                if f"{result.get('firstName') or ''} {result.get('lastName') or ''}".strip().lower()
                not in superseded
            ]
            return [
                Effect(
                    type=EffectType.AUTOCOMPLETE_SEARCH_RESULTS,
                    payload=json.dumps(ours + theirs),
                )
            ]
        def no_opinion(self):
            """A null payload means "keep whatever the search already found"."""
            return [
                Effect(type=EffectType.AUTOCOMPLETE_SEARCH_RESULTS, payload=json.dumps(None))
            ]
    ```
For Refer and Imaging Order, subscribe to those two events instead and swap `as_search_contact()` for `as_search_result()`. The merge is the same, except the directory's results carry their display name in `text` rather than in `firstName` / `lastName`.
A few things worth carrying over into your own version:
  - **Filter to`is_active=True`.** Deactivating a provider is how a customer retires it, so offering a deactivated one invites picking it again.
  - **Return a null payload when you have nothing to add** , rather than an empty list. On the contact surfaces an empty list clears the results.
  - **Prefer your own record when it duplicates a directory contact.** Selecting your record threads its `service_provider_id` through to the commit, so the existing row is reused instead of a near-duplicate being written. Match conservatively — listing a provider twice is a smaller failure than hiding one.
  - **Annotate what you add** so the user can tell your entries from the directory's.
##  Accessing User Context 
PRE_SEARCH and POST_SEARCH events include information about the user performing the search in the event context. This includes search events for command fields like prescriber, medication, diagnosis, pharmacy, and many others. It also includes the non-command fax recipient and external care team directory searches listed under [Other Events](/sdk/events/#other-events). All of these searches can be customized the same way.
You can access the user's staff id from the context:
    ```python
    def compute(self):
        user_context = self.context.get("user", {})
        staff_key = user_context.get("staff")
        # Use the staff id to customize search results
        # based on the user's role, preferences, or permissions
    ```
This can be useful for customizing search results based on:
  - User-specific preferences or settings
  - Role-based filtering (e.g., showing different prescriber options based on the user's specialty)
  - Permission-based access control
  - User's organization or practice location
##  Watch Me Build It 
----- END PAGE https://docs.canvasmedical.com/guides/customize-search-results/


----- BEGIN PAGE https://docs.canvasmedical.com/guides/growth-charts/
The Canvas SDK gives you access to real-time patient data and allows you to create custom UIs accessible from a patient's chart. The combination of these capabilities allows you to create rich, interactive data visualizations your clinicians can access directly in the charting interface. This example will show you how we've used this approach to implement a pediatric growth charts feature.
##  In this guide you will learn how to: 
  - Use the [`ActionButton`](/sdk/handlers-action-buttons) handler to provide a button, specify its location, and define its action in the UI.
  - Use the [`LaunchModalEffect`](/sdk/layout-effect/#modals) to display your custom visualization.
  - Fetch patient [observations](/sdk/data-observation/), such as height and weight, from the [data module](/sdk/data/).
  - Use the patient observation data within an HTML template.
  - Combine all of the above to surface a pediatric growth chart visualization right in the chart.
##  Growth Charts 
Growth charts are percentile curves showing the distribution of selected body measurements in children. A growth chart shows how a child's height, weight, and head circumference (for infants) compare to other children of the same age and sex. It helps track a child's growth over time and can indicate whether they are growing at an expected rate. Growth charts are commonly used by doctors to monitor development and identify potential health concerns. In this guide we will use charts from the [Center for Disease Control and Prevention (CDC)](https://www.cdc.gov/growthcharts/who-data-files.htm) to demonstrate how you can create your own.
The complete plugin is open-source and can be found in the [Medical Software Foundation GitHub repo](https://github.com/Medical-Software-Foundation/canvas/tree/main/extensions/growth_charts/).
The plugin adds a button on the Vital Signs section of the patient chart that, when clicked, launches a modal displaying the patient's height, weight, and head circumference measurements graphed against various percentile curves.
![vitals action button](/assets/images/vitals-action-button.png)
![chart template](/assets/images/growth-charts.png)
In the following steps, we'll show you how we used the Canvas SDK to create it.
##  Adding a button 
To add a button to the vital signs section, you'll implement an [`ActionButton`](/sdk/handlers-action-buttons) handler. In your handler class, you'll set the `BUTTON_LOCATION` constant to `ActionButton.ButtonLocation.CHART_SUMMARY_VITALS_SECTION` to make the action button appear in the corresponding summary section of the chart.
    ```python
    from canvas_sdk.handlers.action_button import ActionButton
    from canvas_sdk.effects import Effect
    class GenerateVitalsGraphs(ActionButton):
        BUTTON_TITLE = "Growth Charts"
        BUTTON_KEY = "show_growth_charts"
        BUTTON_LOCATION = ActionButton.ButtonLocation.CHART_SUMMARY_VITALS_SECTION
        def handle(self) -> list[Effect]:
            # This method is invoked when the button is clicked.
            pass
    ```
##  Launching a modal when the button is clicked 
Now that you have your button showing in the chart section, you can launch a modal when it's clicked using the [`LaunchModalEffect`](/sdk/layout-effect/#modals)
In this guide, we are launching a modal, but this click action can result in any [effect](/sdk/effects/) your handling code returns.
Here's an example of a simple plugin using the `LaunchModalEffect` to display a "Hello World" message.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.launch_modal import LaunchModalEffect
    from canvas_sdk.handlers.action_button import ActionButton
    from canvas_sdk.templates import render_to_string
    class HelloWorld(ActionButton):
        BUTTON_TITLE = "Hello World"
        BUTTON_KEY = "show_hello_world"
        BUTTON_LOCATION = ActionButton.ButtonLocation.NOTE_HEADER
        def handle(self) -> list[Effect]:
            launch_modal = LaunchModalEffect(content=render_to_string("templates/hello-world.html", { "title": "hello world" }))
            return [launch_modal.apply()]
    ```
And here's the result!
![action button](/assets/images/action-button-hello-world.png)
![hello world](/assets/images/template-hello-world.png)
##  Using HTML templates in the modal 
The use of templates allows us to render any kind of information from the data we have. We can even import external libraries from CDNs and add CSS styles to customize and enhance our modal.
To draw our graphs we use d3js, a free, open-source JavaScript library for visualizing data. How to use d3js is outside the scope of this guide, but you can find great documentation and tutorials on the d3 website [here](https://d3js.org/getting-started)
Here's an example of a template file name `hello-world.html` that receives a variable called title, which will be used inside the template.
    ```html
    <!DOCTYPE html>
    <style>
        body {
            font-family: Arial, sans-serif;
            font-size: 30px;
        }
    </style>
    <script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
    <html lang="en">
        <h1 id="main"></h1>
    </html>
    <script>
        const div = document.getElementById('main');
        div.textContent = ``;
    </script>
    ```
##  Fetching the observations 
To retrieve the patient data we use the [Data Module](/sdk/data/). Specifically, the [`Observation`](/sdk/data-observation/) model.
Here, we can use `self.target`, which is the patient's `id`, to retrieve the patient and their observations by filtering for the values we need — such as weight, height, etc.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.v1.data.observation import Observation
    from canvas_sdk.v1.data.patient import Patient
    def handle(self) -> list[Effect]:
        patient = Patient.objects.get(id=self.target)
        sex_at_birth = patient.sex_at_birth
        birth_date = patient.birth_date
        observation_weight = Observation.objects.for_patient(self.target).filter(name="weight")
        observation_length = Observation.objects.for_patient(self.target).filter(name="length")
        observation_bmi = Observation.objects.for_patient(self.target).filter(name="bmi")
        observation_head_circumference = Observation.objects.for_patient(self.target).filter(name="head_circumference")
    ```
##  Tying it all together 
We now know:
  1. How to put a button in the chart
  2. How to launch a modal when that button is clicked
  3. How to create an HTML template to render within that modal
  4. How to retrieve data to use within that HTML template
All that's left is combining these actions into a useful data visualization for our clinical users.
You can see our HTML template [here](https://github.com/Medical-Software-Foundation/canvas/blob/main/extensions/growth_charts/templates/chart.html), which draws many charts based on the observations provided and the percentile curve data provided by the CDC. To make that percentile data usable in our template, we transformed the Excel files from the CDC website into structured data we could use programatically. You can find those python representations of the percentile data [here](https://github.com/Medical-Software-Foundation/canvas/tree/main/extensions/growth_charts/graphs).
Here's an excerpt:
    ```python
    who_boys_length_age = [
        { "x": 0, "y": 46.77032, "z": "5th" },
        { "x": 1, "y": 51.52262, "z": "5th" },
        { "x": 2, "y": 55.13442, "z": "5th" },
        ...,
        { "x": 23, "y": 92.93123, "z": "98th" },
        { "x": 24, "y": 93.92634, "z": "98th" }
    ]
    ```
In addition to the percentile data series, we of course need to plot the patient observation data series. To do this, we create a lists of x and y values corresponding to the patient's age and measurements. You can see this in more detail in the [GitHub repo](https://github.com/Medical-Software-Foundation/canvas/blob/5e8a3dfdb18307e596d2da2d9fce33a3e379cd11/extensions/growth_charts/protocols/growth_charts.py#L80), but here's an excerpt:
    ```python
    import arrow
    import datetime
    from canvas_sdk.effects import Effect
    from canvas_sdk.v1.data.note import Note
    from canvas_sdk.v1.data.observation import Observation
    from canvas_sdk.v1.data.patient import Patient
    def convert_oz_to_kg(oz: str) -> float:
        return float(oz) * 0.0283495
    def get_age_in_months(birth_date: datetime.date, date: datetime.date = datetime.datetime.now()) -> int:
        now = arrow.get(date)
        date = arrow.get(birth_date)
        year_difference = now.year - date.year
        month_difference = now.month - date.month
        return year_difference * 12 + month_difference
    def handle(self) -> list[Effect]:
        graphs = []
        patient = Patient.objects.get(id=self.target)
        sex_at_birth = patient.sex_at_birth
        birth_date = patient.birth_date
        age_in_months = get_age_in_months(birth_date)
        is_less_than_24_months_old = age_in_months < 24
        is_less_than_36_months_old = age_in_months < 36
        observation_weight = Observation.objects.for_patient(self.target).filter(name="weight")
        observation_height = Observation.objects.for_patient(self.target).filter(name="height")
        observation_length = Observation.objects.for_patient(self.target).filter(name="length")
        observation_bmi = Observation.objects.for_patient(self.target).filter(name="bmi")
        observation_head_circumference = Observation.objects.for_patient(self.target).filter(name="head_circumference")
        weight_for_age = {}
        length_for_age = {}
        weight_for_length = {}
        head_for_age = {}
        bmi_for_age = {}
        for obs in observation_weight:
            if obs.value:
                note = Note.objects.get(dbid=obs.note_id)
                age_in_months = get_age_in_months(birth_date, note.datetime_of_service)
                weight_in_kg = convert_oz_to_kg(obs.value)
                weight_for_age[age_in_months] = weight_in_kg
        # ... repeat for other data series
    ```
Finally, we create a list of graphs with the necessary variables and data, which are passed to the template to generate and render the graph.
    ```python
    from canvas_sdk.effects.launch_modal import LaunchModalEffect
    from canvas_sdk.handlers.base import BaseHandler
    from canvas_sdk.templates import render_to_string
    who_boys_length_age = ... # from growth_charts.graphs.who_boys_length_age
    class Handler(BaseHandler):
        def compute(self):
            # list of graphs
            length_for_age = {} # filled in the actual implementation, see linked GitHub repository
            graphs = [
                {
                    "data": who_boys_length_age, # the data from the graph file
                    "title": 'Length for age (Boys 0 - 2 years)', # graph title
                    "xType": 'Generic', # the type of x axis (Generic, Length, Weight) - We need this info to convert the values 
                    "yType": 'Length', # the type of y axis (Generic, Length, Weight)
                    "xLabel": 'Age', # label for the x axis
                    "yLabel": 'Length', # label for the y axis
                    "zLabel": 'Percentile', # label for the z axis
                    "layerData": length_for_age, # the patient's data that will be plotted on the graph
                    "tab": "WHO" # the section where the graph should be rendered (WHO or CDC)
                },
                # ...repeat for other data series
            ]
            launch_modal = LaunchModalEffect(
                content=render_to_string("templates/chart.html", {"graphs": graphs}),
            )
            return [launch_modal.apply()]
    ```
![chart template](/assets/images/growth-charts.png)
By combining action buttons, the data module, and the `LaunchModalEffect`, you can create unique visualizations to help contextualize patient data for your clinical users.
----- END PAGE https://docs.canvasmedical.com/guides/growth-charts/


----- BEGIN PAGE https://docs.canvasmedical.com/guides/patient-chart-group-items/
This guide explains how to group items on a patient chart section. With this, you can define custom groups to organize medications, conditions, or detected issues.
Currently, this is supported for the Conditions, Medications, and Detected Issues sections.
##  What you'll learn: 
  - Use the [`PatientChartGroup`](/sdk/patient-chart-group-effect) effect to group items in a section by priority.
  - Group conditions based on ICD-10 code ranges
  - Group detected issues (such as coding gaps) by custom criteria
##  Patient Chart Group 
The `PatientChartGroup` effect allows you to group items in a patient chart section. You can define multiple groups with a name, priority, and the items that belong to each group.
####  3\. The plugin 
Here's an example of a plugin that groups conditions based on their codes - here we are creating a "Psychiatry" group for ICD-10 codes F01-F99 and R45.x and placing all matching conditions in that group.
    ```python
    from canvas_sdk.effects.patient_chart_group import PatientChartGroup
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.group import Group
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.commands.constants import CodeSystems
    class Handler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.PATIENT_CHART__CONDITIONS)
        def compute(self) -> list[Effect]:
            groups: dict[str, Group] = {}
            groups.setdefault("Psychiatry", Group(priority=100, items=[], name="Psychiatry"))
            for condition in self.event.context:
               for coding in condition["codings"]:
                   if coding["system"] == CodeSystems.ICD10 and ("F01" <= coding["code"] <= "F99" or coding["code"].startswith("R45.")):
                       groups["Psychiatry"].items.append(condition)
                       break
            return [PatientChartGroup(items=groups).apply()]
    ```
####  4\. The Output 
Below, you can see how it will appear in the app.
![patient chart group](/assets/images/patient-chart-group.png)
##  Grouping Detected Issues 
You can also group detected issues, such as coding gaps, using the same approach. Here's an example that groups detected issues by their status or other criteria:
    ```python
    from canvas_sdk.effects.patient_chart_group import PatientChartGroup
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.group import Group
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class GroupDetectedIssues(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.PATIENT_CHART__DETECTED_ISSUES)
        def compute(self) -> list[Effect]:
            groups: dict[str, Group] = {}
            groups.setdefault("High Priority", Group(priority=200, items=[], name="High Priority"))
            groups.setdefault("Standard", Group(priority=100, items=[], name="Standard"))
            for detected_issue in self.event.context:
                # Group by custom logic - for example, based on evidence or other attributes
                # Note: The context for detected issues contains only the "id" field
                # You may need to query additional data using the SDK if needed
                # Example: place first 5 in high priority, rest in standard
                if len(groups["High Priority"].items) < 5:
                    groups["High Priority"].items.append(detected_issue)
                else:
                    groups["Standard"].items.append(detected_issue)
            return [PatientChartGroup(items=groups).apply()]
    ```
**Note:** The context structure for detected issues differs from conditions and medications. The `PATIENT_CHART__DETECTED_ISSUES` event context includes only the `id` field for each detected issue. If you need additional information (such as evidence, status, or code) to determine grouping logic, you'll need to query the detected issue data using the Canvas SDK's data models.
----- END PAGE https://docs.canvasmedical.com/guides/patient-chart-group-items/


----- BEGIN PAGE https://docs.canvasmedical.com/guides/profile-additional-fields/
This guide explains how to create additional fields that will appear on the patient profile demographics form. With this, you can define custom fields, and the information will be stored as patient metadata.
##  What you'll learn: 
  - Use the [`PatientMetadataCreateForm`](/sdk/patient-metadata-create-form-effect) effect to display additional fields on the patient profile.
  - Use the [`FormField`](/sdk/patient-metadata-create-form-effect/#formfield) class to create fields
##  Patient Metadata Create form plugin 
####  1\. FormField 
To create the form, we need to specify which items will be included. For this, we use the [`FormField`](/sdk/patient-metadata-create-form-effect/#formfield) class, where we can define our inputs and their attributes.
    ```python
    from canvas_sdk.effects.patient_metadata import InputType, FormField
    FormField(
        key='musicGenre',
        label='Preferred music genre',
        type=InputType.TEXT,
        required=False,
        editable=True,
    )
    ```
####  2\. PatientMetadataCreateFormEffect 
The next step is to add these fields to the effect so they can be used to build the form.
    ```python
    from canvas_sdk.effects.patient_metadata import PatientMetadataCreateFormEffect, InputType, FormField
    PatientMetadataCreateFormEffect(form_fields=[
        FormField(
            key='musicGenre',
            label='Preferred music genre',
            type=InputType.TEXT,
            required=False,
            editable=True,
        ),
        ...,
    ])
    ```
####  3\. The plugin 
Here's an example of a complete plugin showcasing the different input types.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.patient_metadata import PatientMetadataCreateFormEffect, InputType, FormField
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    # Inherit from BaseHandler to properly get registered for events
    class Handler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.PATIENT_METADATA__GET_ADDITIONAL_FIELDS)
        def compute(self) -> list[Effect]:
            form = PatientMetadataCreateFormEffect(form_fields=[
                FormField(
                    key='musicGenre',
                    label='Preferred music genre',
                    type=InputType.TEXT,
                    required=False,
                    editable=True,
                ),
                FormField(
                    key='occupation',
                    label='Occupation',
                    type=InputType.SELECT,
                    required=False,
                    editable=True,
                    options=["Engineer", "Teacher", "Other"]
                ),
                FormField(
                    key='date',
                    label='Date',
                    type=InputType.DATE,
                    required=False,
                    editable=True,
                ),
            ])
            return [form.apply()]
    ```
####  4\. The Output 
And that's it! Below, you can see how it will appear in the app — these fields will be stored as patient metadata.
![medication widget](/assets/images/additional-fields.png)
----- END PAGE https://docs.canvasmedical.com/guides/profile-additional-fields/


----- BEGIN PAGE https://docs.canvasmedical.com/guides/scribe-ai-parser/
The [AI Scribe Parser Plugin](https://github.com/Medical-Software-Foundation/canvas/tree/main/extensions/ai-scribe) was designed to help streamline clinical documentation by parsing structured transcripts into commands. With healthcare providers increasingly adopting AI-driven solutions, this guide provides developers with the insights and instructions needed to integrate and extend our example plugin to meet diverse documentation needs. In this guide, you'll learn how to:
  - Intercept [`CLIPBOARD_COMMAND__POST_INSERTED_INTO_NOTE`](/sdk/events/#clipboard-command).
  - Use `ScribeParser` (or custom parsers) to process transcripts.
  - Generate commands for each section.
  - Add or replace section parsers for custom sections.
  - Implement a fully custom parser for alternate formats.
##  Understanding the Transcript Parsing Flow. 
The workflow is triggered by pasting a transcript into a note. Doing so will automatically insert the content in the form of a clipboard command. We can then respond to that event and transform the content into the appropriate commands.
###  Input Transcript Example 
Our example transcript includes many structured sections. The sections listed below were flagged as being formatted in a way that makes them easy to translate into Canvas commands.
  - Chief complaint
  - History of present illness
  - Past medical history
  - Vitals
  - Plan
Each section contains specific information that can be parsed. For example:
  - The **Vitals** section includes data like weight, heart rate, and blood pressure, which can be converted into a `VitalsCommand`.
  - The **Assessment** section provides diagnoses and clinical impressions, which can be mapped to an `AssessCommand`.
**Complete Example**
    ```plaintext
    Chief complaint
    - Concern about potential diabetes
    - Hypertension
    History of present illness
    - Patient named Ken, age and gender not mentioned
    - Has sleep apnea, uses CPAP machine
    - Has hyperlipidemia
    - Has hypertension, on medication but doesn't remember the names
    - No other symptoms or issues reported
    - No shortness of breath or pain reported
    Past medical history
    - Sleep apnea
    - Hyperlipidemia
    - Hypertension
    Family history
    No known family history of hypertension
    Social history
    Travels a lot
    Current medications
    Medication for hypertension, names not provided
    Vitals
    - Weight: 244 lbs
    - Height: 5'10"
    - Heart rate: 80
    - Oxygen saturation: 94%
    - Blood pressure: 167/106
    Lab results
    A1C: 5.2 (Normal range, neither prediabetic nor diabetic)
    Physical exam
    CARDIOVASCULAR: Heart sounds good.
    LUNGS: Lungs sound good.
    Assessment
    - Hypertension, not well controlled
    - Sleep apnea, using CPAP machine
    - Hyperlipidemia
    - Elevated BMI, potential for weight loss intervention
    - No diabetes or prediabetes
    Plan
    - Recommendation for weight loss services
    - Recommendation to address hypertension, either at this clinic or with primary care
    - Potential adjustment of hypertension medication
    - Offered subscription program with unlimited office visits and access to a nutritionist
    - Potential telemedicine consultations due to patient's frequent travel
    Appointments
    No specific appointment made, patient to contact clinic after discussing with wife
    ICD-10 codes (3)
    - Sleep apnea, unspecified [G47.30]
    - Hyperlipidemia, unspecified [E78.5]
    - Essential (primary) hypertension [I10]
    ```
###  AI Scribe Plugin Architecture 
Once the content is pasted in, the plugin does the rest. Here's how.
####  1\. Handler Class 
The `Handler` class intercepts events and processes the transcript using a parser.
    ```python
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.effects import Effect
    from canvas_sdk.events import EventType
    class ScribeParser: ...  # explained in section 2 below, "ScribeParser"
    class Handler(BaseHandler):
        """A Plugin for interpreting transcripts."""
        RESPONDS_TO = EventType.Name(EventType.CLIPBOARD_COMMAND__POST_INSERTED_INTO_NOTE)
        def compute(self) -> list[Effect]:
            """Parse the transcript and generate effects to originate commands."""
            transcript = self.context["fields"]["text"]
            parser = ScribeParser()
            parsed_transcript = parser.parse(transcript, self.context)
            note_uuid = self.context["note"]["uuid"]
            effects = []
            for commands in parsed_transcript.values():
                for command in commands:
                    command.note_uuid = note_uuid
                    effects.append(command.originate(line_number=1))
            effects.reverse()
            return effects
    ```
####  2\. ScribeParser 
The `ScribeParser` delegates the parsing of each transcript section to specific section parsers.
    ```python
    from ai_scribe.parsers.base import TranscriptParser
    # explained in section 3 below, "Section Parsers"
    class ChiefComplaintParser: ...
    class HistoryOfPresentIllnessParser: ...
    class PastMedicalHistoryParser: ...
    class PlanParser: ...
    class VitalsParser: ...
    class ScribeParser(TranscriptParser):
        """A parser for scribe transcripts."""
        section_parsers = {
            "chief_complaint": ChiefComplaintParser(),
            "history_of_present_illness": HistoryOfPresentIllnessParser(),
            "past_medical_history": PastMedicalHistoryParser(),
            "vitals": VitalsParser(),
            "plan": PlanParser(),
        }
        def parse(self, transcript: str, context: dict) -> dict:
            """Parse the transcript into commands grouped by sections."""
            parsed_sections = {}
            for section, parser in self.section_parsers.items():
                parsed_sections[section] = parser.parse(transcript, context)
            return parsed_sections
    ```
####  3\. Section Parsers 
Each section parser extracts relevant information from its section and produces commands.
    ```python
    from typing import Any, Sequence
    from ai_scribe.parsers.base import CommandParser, ParsedContent
    from canvas_sdk.commands.commands.plan import PlanCommand
    class PlanParser(CommandParser):
        """Parses the plan section of a transcript."""
        def parse(self, content: ParsedContent, context: Any = None) -> Sequence[PlanCommand]:
            """Parses the plan section of a transcript."""
            return [PlanCommand(narrative=line) for line in content["arguments"]]
    ```
##  Extending the Parser 
###  1\. Adding a New Section Parser 
Suppose you want to parse the "Appointments" section into a `TaskCommand` for follow-up tasks.
####  Define the Parser 
    ```python
    from typing import Sequence, Any
    from canvas_sdk.commands import TaskCommand
    from ai_scribe.parsers.base import CommandParser, ParsedContent
    class AppointmentsParser(CommandParser):
        """Parses the 'Appointments' section of a transcript."""
        def parse(self, content: ParsedContent, context: Any = None) -> Sequence[TaskCommand]:
            """Parses the Appointments section and generates TaskCommands."""
            tasks = []
            for line in content["arguments"]:
                tasks.append(TaskCommand(title=line))
            return tasks
    ```
####  Register the Parser 
Add the `AppointmentsParser` to the `section_parsers` dictionary.
    ```python
    class AppointmentsParser: ... # defined above
    class ScribeParser:
        """A parser for transcripts."""
        section_parsers = {
            "appointments": AppointmentsParser()
        }
    ```
###  2\. Customizing the Entire Parser 
To replace `ScribeParser`, define your custom parser.
    ```python
    from ai_scribe.parsers.base import (
        ParsedContent,
        TranscriptParser,
        TranscriptParserOutput,
    )
    class CustomParser(TranscriptParser):
        """Custom parser for alternative transcript formats."""
        def parse(self, transcript: str, context: dict) -> dict:
            """Parse the transcript and produce commands."""
            # Implement custom parsing logic
            ...
    ```
Replace the parser in the `Handler` class:
    ```python
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.effects import Effect
    class CustomParser: ... # defined above
    class Handler(BaseHandler):
        """Handler using a custom parser."""
        def compute(self) -> list[Effect]:
            transcript = self.context["fields"]["text"]
            parser = CustomParser()  # Use custom parser
            parsed_transcript = parser.parse(transcript, self.context)
            note_uuid = self.context["note"]["uuid"]
            effects = []
            for commands in parsed_transcript.values():
                for command in commands:
                    command.note_uuid = note_uuid
                    effects.append(command.originate(line_number=1))
            effects.reverse()
            return effects
    ```
##  Watch the Workflow in Action 
##  Conclusion 
With robust parsing capabilities and extensibility, this example plugin equips developers to support clinicians in reclaiming their time for what matters most: patient care. By following the steps in this guide, developers can ensure seamless integration into clinical workflows, while also tailoring the tool to suit specific needs.
----- END PAGE https://docs.canvasmedical.com/guides/scribe-ai-parser/


----- BEGIN PAGE https://docs.canvasmedical.com/guides/set-default-homepage/
This guide explains how to set a default homepage for the provider application. By setting a default homepage, you can control which page users see when they first log in to the provider application, ensuring they have quick access to the most relevant information or features.
##  What you'll learn: 
  - Use the [`Application`](/sdk/data-application) model to get a specific application.
  - Use the [`DefaultHomepageEffect`](/sdk/default-homepage-effect) to set the default homepage.
##  Default homepage plugin 
####  1\. Set an application as the homepage 
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.default_homepage import DefaultHomepageEffect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.v1.data import Application
    class Homepage(BaseHandler):
        """Handler for homepage configuration events."""
        RESPONDS_TO = EventType.Name(EventType.GET_HOMEPAGE_CONFIGURATION)
        def compute(self) -> list[Effect]:
            """Set an application as the default homepage."""
            application = Application.objects.filter(name="custom_homepage").first()
            return [DefaultHomepageEffect(application_identifier=application.identifier).apply()]
    ```
####  2\. Set a specific page as the homepage 
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.default_homepage import DefaultHomepageEffect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class Homepage(BaseHandler):
        """Handler for homepage configuration events."""
        RESPONDS_TO = EventType.Name(EventType.GET_HOMEPAGE_CONFIGURATION)
        def compute(self) -> list[Effect]:
            """Set the Patients page as the default homepage."""
            return [DefaultHomepageEffect(page=DefaultHomepageEffect.Pages.PATIENTS).apply()]
    ```
----- END PAGE https://docs.canvasmedical.com/guides/set-default-homepage/


----- BEGIN PAGE https://docs.canvasmedical.com/guides/tailoring-the-chart-to-the-patient/
Different patients have different needs, and your tools should reflect that. EMRs are able to be used in a wide variety of scenarios. In order to be able to do just about anything, your EMR is packed to the gills with features and options. While you might need all of these features for all of your patients, you almost certainly don't need _every_ feature for _each_ of your patients.
This guide shows a few examples of using a patient's data to customize their chart so the EMR features most relevant to them are front-and-center, while minimizing or hiding what you don't need in the moment. Tailoring the interface based on the patient in front of you creates a focused environment for you to deliver care without irrelevant options getting between you and your patient.
> **Info:** This guide assumes pre-existing knowledge of the Canvas SDK. If you're starting from scratch, you may want to read and implement [Your First Plugin (with Claude Code)](/guides/your-first-plugin-with-claude-code/) before working through this exercise. 
##  Chart Customizations for Pediatric Patients 
We will make two simple changes to the charting interface when the selected patient is a child:
  1. Move the Immunization list to the top of the patient summary
  2. Prevent adult-only diagnosis choices from appearing in searches
First, we'll need to initialize a new plugin.
The Canvas CLI gives you a great head start when creating a plugin. Simply run `canvas init`, and answer the prompt to name your plugin.
    ```sh
    $ canvas init
      [1/1] project_name (My Cool Plugin): Pediatric Patient Chart Customizations
    Project created in /Users/andrew/src/canvas-plugins/pediatric-patient-chart-customizations
    ```
This output shows the location of our freshly generated plugin project. In this directory, you'll see a default class (`pediatric_patient_chart_customizations/handlers/event_handlers.py`) provided as a starting point for your code.
    ```sh
    $ tree pediatric_patient_chart_customizations/
    pediatric_patient_chart_customizations/
    ├── CANVAS_MANIFEST.json
    ├── README.md
    └── handlers
        ├── __init__.py
        └── event_handlers.py
    2 directories, 4 files
    ```
You can use this file as a starting point, or you can start fresh with a new file. At minimum, I recommend renaming `event_handlers.py` to something more descriptive, and you'll need to update the references to the file in `CANVAS_MANIFEST.json` as well.
###  Move Immunizations to the Top of the Patient Summary 
I've created a new file, `handlers/pediatric_chart_layout.py`, and I've updated my `CANVAS_MANIFEST.json` to reflect it.
    ```sh
    $ tree pediatric_patient_chart_customizations/
    pediatric_patient_chart_customizations/
    ├── CANVAS_MANIFEST.json
    ├── README.md
    └── handlers
        ├── __init__.py
        └── pediatric_chart_layout.py
    2 directories, 4 files
    ```
Here is a pretty empty class with some comments that will guide our development:
    ```python
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers.base import BaseHandler
    class PediatricChartLayout(BaseHandler):
        """
        This event handler rearranges the patient summary section to focus on the
        parts most relevant to pediatric patients when it detects that the patient
        for the current chart is <= 17 years old.
        """
        # This event fires when a patient chart's summary section is loading.
        RESPONDS_TO = EventType.Name(EventType.PATIENT_CHART_SUMMARY__SECTION_CONFIGURATION)
        def compute(self):
            """
            Check to see if the patient is <= 17 years old. If so, move their
            immunization list to the top of the patient summary.
            """
            # Look up the patient whose chart is being loaded right now
            # See if the patient is younger than 18 years old
            # If they are not younger than 18, do nothing
            # If they are younger than 18, re-arrange the layout of their summary
            # sections to put immunizations at the top.
            # BaseHandler subclasses must return a list, but it can be empty. It
            # is empty here since we aren't doing anything just yet.
            return []
    ```
Pretty straightforward logic. Do nothing or do something based on their age.
####  Looking Up the Patient 
In order to determine if we are on a pediatric chart, we'll need to know the patient and their birth date. We can use the [Patient class](/sdk/data-patient/) in the [Data Module](/sdk/data/) for this.
The `PATIENT_CHART_SUMMARY__SECTION_CONFIGURATION` event is accompanied by the id of the patient whose chart is being loaded. You can find it using `self.target`. The patient is the target of the event.
    ```python
    import arrow
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers.base import BaseHandler
    from canvas_sdk.v1.data.patient import Patient
    class PediatricChartLayout(BaseHandler):
        """
        This event handler rearranges the patient summary section to focus on the
        parts most relevant to pediatric patients when it detects that the patient
        for the current chart is <= 17 years old.
        """
        # This event fires when a patient chart's summary section is loading.
        RESPONDS_TO = EventType.Name(EventType.PATIENT_CHART_SUMMARY__SECTION_CONFIGURATION)
        def compute(self):
            """
            Check to see if the patient is <= 17 years old. If so, move their
            immunization list to the top of the patient summary.
            """
            eighteen_years_ago = arrow.now().shift(years=-18).date().isoformat()
            patient_is_pediatric = Patient.objects.filter(
                id=self.target, birth_date__gt=eighteen_years_ago).exists()
            # If the patient is not pediatric, do not alter the layout.
            if not patient_is_pediatric:
                return []
            # TODO: Alter the layout
            return []
    ```
In the code above, you'll see I didn't actually retrieve the patient's information. Since I don't plan to use any of the data from the patient record, I instead let the database answer the question: "Does the patient with this id have a birth date more recent than 18 years ago?" This is a performance optimization. The less data transmitted, the faster your plugins execute, and the faster your charts load. This is not strictly necessary, but over time and with enough plugins installed the inefficiencies could add up. If you'd like you could alternately retrieve the patient and make a direct comparison to their `birth_date` attribute.
####  Altering the Layout 
When loading the patient's summary, the front-end consults a list of sections to retrieve data for and render. Our plugin influences this list, and it does so using the [`PatientChartSummaryConfiguration`](https://github.com/canvas-medical/canvas-plugins/blob/main/canvas_sdk/effects/patient_chart_summary_configuration.py) effect. Using the list of possible sections, we construct the ordered list of sections we want to see. In this example we're just moving one to the top, but you could also omit sections to hide them entirely if there are sections you do not use or need for your [Care Model](https://www.canvasmedical.com/articles/care-modeling-the-secret-to-success-in-care-delivery).
    ```python
    import arrow
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers.base import BaseHandler
    from canvas_sdk.effects.patient_chart_summary_configuration import PatientChartSummaryConfiguration
    from canvas_sdk.v1.data.patient import Patient
    class PediatricChartLayout(BaseHandler):
        """
        This event handler rearranges the patient summary section to focus on the
        parts most relevant to pediatric patients when it detects that the patient
        for the current chart is <= 17 years old.
        """
        # This event fires when a patient chart's summary section is loading.
        RESPONDS_TO = EventType.Name(EventType.PATIENT_CHART_SUMMARY__SECTION_CONFIGURATION)
        def compute(self):
            """
            Check to see if the patient is <= 17 years old. If so, move their
            immunization list to the top of the patient summary.
            """
            eighteen_years_ago = arrow.now().shift(years=-18).date().isoformat()
            patient_is_pediatric = Patient.objects.filter(
                id=self.target, birth_date__gt=eighteen_years_ago).exists()
            # If the patient is not pediatric, do not alter the layout.
            if not patient_is_pediatric:
                return []
            layout = PatientChartSummaryConfiguration(sections=[
              PatientChartSummaryConfiguration.Section.IMMUNIZATIONS,
              PatientChartSummaryConfiguration.Section.SOCIAL_DETERMINANTS,
              PatientChartSummaryConfiguration.Section.GOALS,
              PatientChartSummaryConfiguration.Section.CONDITIONS,
              PatientChartSummaryConfiguration.Section.MEDICATIONS,
              PatientChartSummaryConfiguration.Section.ALLERGIES,
              PatientChartSummaryConfiguration.Section.CARE_TEAMS,
              PatientChartSummaryConfiguration.Section.VITALS,
              PatientChartSummaryConfiguration.Section.SURGICAL_HISTORY,
              PatientChartSummaryConfiguration.Section.FAMILY_HISTORY,
            ])
            return [layout.apply()]
    ```
Once installed, pediatric patients will have their immunization section at the top of their summary, while adult patients will continue to have social determinants as their initial section.
###  Prevent adult-only diagnosis choices from appearing in searches 
Some diagnosis codes are restricted to adult patients. CMS provides a [list](https://www.cms.gov/Medicare/Coding/OutpatientCodeEdit/Downloads/ICD-10-IOCE-Code-Lists.pdf) of these "Adult Diagnoses". We can reference this list and filter them out of the diagnosis search results for pediatric patients. This increases the quality of your search and makes it easier for you to find the right choice.
I've created a new file, `handlers/pediatric_condition_search.py`, and I've updated my `CANVAS_MANIFEST.json` to reflect it.
Here's the updated plugin file structure:
    ```sh
    $ tree pediatric_patient_chart_customizations/
    pediatric_patient_chart_customizations/
    ├── CANVAS_MANIFEST.json
    ├── README.md
    └── handlers
        ├── __init__.py
        ├── pediatric_chart_layout.py
        └── pediatric_condition_search.py
    2 directories, 5 files
    ```
And here's the updated `CANVAS_MANIFEST.json`:
    ```json
    {
        "sdk_version": "0.1.4",
        "plugin_version": "0.0.1",
        "name": "pediatric_patient_chart_customizations",
        "description": "Customizations for pediatric patients",
        "components": {
            "handlers": [
                {
                    "class": "pediatric_patient_chart_customizations.handlers.pediatric_chart_layout:PediatricChartLayout",
                    "description": "Moves the immunization section to the top of the patient summary on pediatric charts.",
                    "data_access": {
                        "event": "",
                        "read": [],
                        "write": []
                    }
                },
                {
                    "class": "pediatric_patient_chart_customizations.handlers.pediatric_condition_search:PediatricConditionSearch",
                    "description": "Filters the condition search to eliminate adult-only conditions on pediatric charts.",
                    "data_access": {
                        "event": "",
                        "read": [],
                        "write": []
                    }
                }
            ],
            "commands": [],
            "content": [],
            "effects": [],
            "views": []
        },
        "variables": [],
        "tags": {},
        "references": [],
        "license": "",
        "diagram": false,
        "readme": "./README.md"
    }
    ```
And here's the very basic outline we'll start out with:
    ```python
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers.base import BaseHandler
    ADULT_ONLY_ICD_CODES = {
        # There are many, many more. Limiting this example for brevity.
        # ...
        "Z561",   # Change of job
        "Z5682",  # Military deployment status
        # ...
    }
    class PediatricConditionSearch(BaseHandler):
        """
        Filter condition searches for pediatric patients.
        """
        RESPONDS_TO = [
            EventType.Name(EventType.DIAGNOSE__DIAGNOSE__POST_SEARCH),
            EventType.Name(EventType.MEDICAL_HISTORY__PAST_MEDICAL_HISTORY__POST_SEARCH),
        ]
        def compute(self):
            """
            Remove condition search results representing codings that are resevered
            for adults if the patient is <= 15 years old.
            """
            # This event's target is the command we are searching within. Look up
            # the patient id from the command, and use that to look up the patient.
            # If the patient is not pediatric, do not alter the search.
            # If the patient is pediatric, loop through the search results, and
            # compare the codings of the options with our list of adult-only
            # diagnosis codes. If it's an adult only code, remove it from the
            # list.
            return []
    ```
Using the list provided by CMS, we can create a set of ICD-10 codes that should be restricted to adults. According to CMS, these diagnoses are only relevant to patients 15 or older.
While the layout altering class responded to a single event, we're listening for two different events here: diagnose command search and past medical history command search. Both of these commands include a diagnosis code search box, so we'll want to affect both. We wouldn't want to impact a family history command diagnosis search, since the family members recorded there are often adults.
####  Looking Up the Patient 
In the previous example, the target of the `PATIENT_CHART_SUMMARY__SECTION_CONFIGURATION` event was the patient. For the events we're responding to here, the target is the [command](/sdk/data-command/) that the search is occurring within. In order to determine if the patient is young enough for our code to be invoked, we'll first look up the patient's id from the command, then assess their age in a similar manner as before.
    ```python
    import arrow
    from canvas_sdk.v1.data.command import Command
    from canvas_sdk.v1.data.patient import Patient
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers.base import BaseHandler
    ADULT_ONLY_ICD_CODES = {
        # There are many, many more. Limiting this example for brevity.
        # ...
        "Z561",   # Change of job
        "Z5682",  # Military deployment status
        # ...
    }
    class PediatricConditionSearch(BaseHandler):
        """
        Filter condition searches for pediatric patients.
        """
        RESPONDS_TO = [
            EventType.Name(EventType.DIAGNOSE__DIAGNOSE__POST_SEARCH),
            EventType.Name(EventType.MEDICAL_HISTORY__PAST_MEDICAL_HISTORY__POST_SEARCH),
        ]
        def compute(self):
            """
            Remove condition search results representing codings that are resevered
            for adults if the patient is <= 15 years old.
            """
            # This event's target is the command we are searching within. Look up
            # the patient id from the command.
            patient_id = Command.objects.filter(id=self.target).values_list('patient__id', flat=True).first()
            fifteen_years_ago = arrow.now().shift(years=-15).date().isoformat()
            patient_is_pediatric = Patient.objects.filter(
                id=patient_id, birth_date__gt=fifteen_years_ago).exists()
            # If the patient is not pediatric, do not alter the search.
            # If the patient is pediatric, loop through the search results, and
            # compare the codings of the options with our list of adult-only
            # diagnosis codes. If it's an adult only code, remove it from the
            # list.
            return []
    ```
Once again you see some code that optimizes for performance over readability. If you're not familiar with the Django ORM, this code:
    ```python
    patient_id = Command.objects.filter(id=self.target).values_list('patient__id', flat=True).first()
    ```
Is equivalent to this code, which you may find more readable:
    ```python
    command = Command.objects.get(id=self.target)
    patient_id = command.patient.id
    ```
However you get to the patient's id is up to you. Once you have it, the code is nearly identical to the previous example, the only difference being the age we're targeting. We are asking the database "Does the patient with this id have a birth date more recent than 15 years ago?"
####  Altering the Layout 
Now that we know when we should act, we need to write the code for filtering the list of search results. When the search is performed, the raw results are sent to our code before being ultimately delivered to the dropdown box in the front-end. We have the opportunity to modify that list before it hits the dropdown. While we're focused on removing irrelevant choices, you could also add labels to certain ones you wish to highlight or alter the search order to guide users to preferred options.
Our code loops through the ICD-10 codes associated with the search results, checks for their presence in the set of adult ICD-10 codes, and only includes them in the post-processed set if they are not in the adult code list. The raw search results are in the event's context object under the `results` key.
Here's the full code:
    ```python
    import json
    import arrow
    from canvas_sdk.v1.data.command import Command
    from canvas_sdk.v1.data.patient import Patient
    from canvas_sdk.effects import Effect, EffectType
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers.base import BaseHandler
    ADULT_ONLY_ICD_CODES = {
        # There are many, many more. Limiting this example for brevity.
        # ...
        "Z561",   # Change of job
        "Z5682",  # Military deployment status
        # ...
    }
    class PediatricConditionSearch(BaseHandler):
        """
        Filter condition searches for pediatric patients.
        """
        RESPONDS_TO = [
            EventType.Name(EventType.DIAGNOSE__DIAGNOSE__POST_SEARCH),
            EventType.Name(EventType.MEDICAL_HISTORY__PAST_MEDICAL_HISTORY__POST_SEARCH),
        ]
        def compute(self):
            """
            Remove condition search results representing codings that are resevered
            for adults if the patient is <= 15 years old.
            """
            results = self.context.get("results")
            if results is None:
                return [Effect(type=EffectType.AUTOCOMPLETE_SEARCH_RESULTS, payload=json.dumps(None))]
            # This event's target is the command we are searching within. Look up
            # the patient id from the command.
            patient_id = Command.objects.filter(id=self.target).values_list('patient__id', flat=True).first()
            fifteen_years_ago = arrow.now().shift(years=-15).date().isoformat()
            patient_is_pediatric = Patient.objects.filter(
                id=patient_id, birth_date__gt=fifteen_years_ago).exists()
            # If the patient is not pediatric, do not alter the search.
            if not patient_is_pediatric:
                return [Effect(type=EffectType.AUTOCOMPLETE_SEARCH_RESULTS, payload=json.dumps(None))]
            # Create our container for modified search results
            post_processed_results = []
            # Loop through the ICD 10 codes associated with the results
            for result in self.context["results"]:
                for coding in result.get("extra", {}).get("coding", []):
                    if not coding.get("system") in ("http://hl7.org/fhir/sid/icd-10", "ICD-10"):
                        continue
                    # If the ICD 10 code is not in the list of Adult-only codes,
                    # we can add this result to what will ultimately be returned.
                    if coding.get("code") not in ADULT_ONLY_ICD_CODES:
                        post_processed_results.append(result)
                        break
            # Return our modified search results
            return [
                Effect(
                    type=EffectType.AUTOCOMPLETE_SEARCH_RESULTS,
                    payload=json.dumps(post_processed_results),
                )
            ]
    ```
Once installed, certain diagnosis codes will not clutter up search results in commands for pediatric patients, but will continue to show as expected for patients over the age of 15.
##  Watch the Workflow in Action 
View and deploy the Pediatric Patient Chart Customization Extension [here](https://www.canvasmedical.com/extensions/pediatric-patient-chart-customizations).
##  Conclusion 
Age is one differentiator that changes the relevance of EMR features, but there are many, many others. Using the Canvas SDK can help keep clinicians focused, with the features they need front-and-center and the ones they don't need out of the way.
----- END PAGE https://docs.canvasmedical.com/guides/tailoring-the-chart-to-the-patient/


----- BEGIN PAGE https://docs.canvasmedical.com/guides/your-first-application/
This guide will walk you through the process of installing, initializing, and customizing embeddable applications in Canvas.
##  What Are Applications in Canvas? 
Applications in Canvas are embeddable plugins that enhance the functionality of the Canvas platform. They allow developers to create custom features accessible directly from within the Canvas interface, such as interactive tools, data visualizations, or workflow integrations. These applications can be configured to appear globally or within specific contexts, such as the patient chart page.
##  Step 1: Install and Configure the Canvas CLI 
Follow the instructions in the [Canvas documentation](https://docs.canvasmedical.com/guides/your-first-plugin/#1-install-the-canvas-cli) to install and configure the Canvas CLI. Once complete, ensure that you can successfully run `canvas` commands from your terminal.
##  Step 2: Initialize an Application 
To create a new application, run the following command:
    ```bash
    canvas init application
    ```
This will generate a boilerplate application along with a `CANVAS_MANIFEST.json` file.
##  Step 3: Understanding the `CANVAS_MANIFEST.json` File 
The `CANVAS_MANIFEST.json` file describes your application and its components. Below is an example of a manifest and a description of the customizable properties:
    ```json
    {
        "sdk_version": "0.1.4",
        "plugin_version": "0.0.1",
        "name": "my_cool_application",
        "description": "Edit the description in CANVAS_MANIFEST.json",
        "url_permissions": [
            {
                "url": "https://my-application.com",
                "permissions": []
            }
        ],
        "components": {
            "applications": [
                {
                    "class": "my_cool_application.applications.my_application:MyApplication",
                    "name": "My Application",
                    "description": "An Application that does xyz...",
                    "scope": "global",
                    "icon": "assets/python-logo.png",
                    "menu_position": "top",
                    "menu_order": "100",
                    "show_in_panel": false,
                    "panel_priority": 100
                }
            ],
            "commands": [],
            "content": [],
            "effects": [],
            "views": []
        },
        "variables": [],
        "tags": {},
        "references": [],
        "license": "",
        "diagram": false,
        "readme": "./README.md"
    }
    ```
###  Customizable Properties 
  1. **name** : The display name of the application.
  2. **description** : A brief description of the application.
  3. **icon** : The icon for the application. This can be a URL to an image or a path inside the plugin package.
  4. **scope** : 
     - `global`: The app will appear across all contexts.
     - `patient_specific`: The app will appear only in the patient chart page.
     - `provider_menu_item`: The app button will be displayed on the provider's menu.
     - `portal_menu_item`: The app button will be displayed on the patient portal menu.
  5. **url_permissions** : The allowed urls and permissions for the application. This is used for security purposes. For more info check the [Application Handler](/sdk/
handlers-applications).
  6. **menu_position** : Determines where the menu item will be placed within the menu (this configuration applies only to the providers menu) 
     - `top`: The item will be placed on the top section.
     - `bottom`: The item will be placed in the bottom section - this section should display items that open in a new window.
  7. **menu_order** : How the items will be ordered in the menu. e.g 100, 200
  8. **show_in_panel** : If you want to increase your application's visibility and display it alongside other panel buttons (instead of in the applications drawer), you can set this attribute
  9. **panel_priority** : How the applications will be ordered in the panel section. e.g 100, 200
##  Step 4: Overriding the Application Behavior 
Developers must extend the `Application` class and override the `on_open` method to define the behavior when the app icon is clicked. Below is an example implementation:
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.launch_modal import LaunchModalEffect
    from canvas_sdk.handlers.application import Application
    class MyApplication(Application):
        """An embeddable application that can be registered to Canvas."""
        def on_open(self) -> Effect:
            """Handle the on_open event."""
            # Implement this method to handle the application on_open event.
            return LaunchModalEffect(
                url="http://localhost:8000",
                target=LaunchModalEffect.TargetType.DEFAULT_MODAL
            ).apply()
    ```
###  Key Details 
  - **`on_open` Method**: 
    - Called when the user clicks on the app icon.
    - Should return a `LaunchModalEffect` that specifies: 
      - **url** : The URL to open.
      - **target** : The display target.
###  Target Options 
  - `DEFAULT_MODAL`: Opens the URL in a modal centered on the screen.
  - `NEW_WINDOW`: Opens the URL in a new browser window.
  - `PAGE`: Opens the URL as a page in the app
  - `RIGHT_CHART_PANE`: Opens the URL in the right-hand pane of the patient chart.
  - `RIGHT_CHART_PANE_LARGE`: Opens the URL in an enlarged right-hand pane of the patient chart.
##  Step 5: Installing the Application 
To install your application, run:
    ```bash
    canvas install <path/to/application>
    ```
----- END PAGE https://docs.canvasmedical.com/guides/your-first-application/


----- BEGIN PAGE https://docs.canvasmedical.com/guides/your-first-plugin-with-claude-code/
Canvas plugins let you customize the EHR — reacting to events, reading patient data, and returning effects that alter workflows. This guide uses an AI-assisted approach powered by Claude Code and the Canvas Plugin Assistant (CPA), getting you from idea to deployed plugin in minutes.
> **Info:** Haven't yet advanced to AI-assisted coding? See [Your First Plugin (Manual)](/guides/your-first-plugin/) to build your plugin by hand. 
##  Background: AI-Assisted Plugin Development 
[Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview) is Anthropic's agentic coding tool — it can read, write, and execute code autonomously in your terminal. Rather than copying and pasting snippets from documentation or typing code by hand, you can describe what you want in plain English and Claude Code does the programming with your supervision.
Claude Code supports a plugin marketplace. We built the Canvas Plugin Assistant (CPA) Claude Code plugin (yes, a plugin to make plugins!) with deep knowledge of the Canvas SDK, common plugin patterns, and best-practice workflows in plugin development.
CPA accelerates the entire plugin development lifecycle — from brainstorming requirements and scaffolding code, to running tests and deploying to your instance and rapidly cycling through user acceptance testing and enhancements.
Together, Claude Code with CPA lets developers go from an idea to a working, deployed plugin through natural conversation at warp speed.
##  Prerequisites 
  - Python 3.12+ installed
  - A Canvas instance with admin access
  - [OAuth credentials configured](/api/customer-authentication/) and saved locally in `~/.canvas/credentials.ini` (register an application with `confidential` client type and `client-credentials` grant type). For more, see [these configuration steps](/guides/your-first-plugin/#2-configure-the-canvas-cli-for-your-instances)
  - [Claude Code installed](https://docs.anthropic.com/en/docs/claude-code/overview)
##  1\. Install the Canvas Plugin Assistant and Set Up Your Environment 
Follow the installation and setup instructions in the [Canvas Plugin Assistant README](https://github.com/canvas-medical/coding-agents/tree/main/canvas-plugin-assistant). Once complete, run `/cpa:check-setup` to verify everything is ready.
##  2\. Describe Your First Plugin 
Run `/cpa:new-plugin` to start building. CPA will ask clarifying questions via an interactive chip interface. Describe what you want in plain English. You can find examples of what's possible [here](https://github.com/Medical-Software-Foundation/canvas/tree/main/extensions), [here](https://github.com/canvas-medical/canvas-plugins/tree/main/example-plugins), and [here](https://www.canvasmedical.com/plugins). For example:
> I want a plugin that adds a button to the note header. When clicked, the button should inject a Reason for Visit command into the note with the text "hello world".
Here's what happens next:
  1. **Brainstorm** — CPA asks follow-up questions to refine your requirements
  2. **Specification** — CPA generates a `plugin-spec.md` for your review. Read through it and approve or request changes
  3. **Scaffold** — CPA creates the [plugin project structure](/guides/your-first-plugin/#4-navigate-the-structure-of-a-plugin), manifest, and handler files
  4. **Implement** — CPA writes the protocol/handler code based on the spec
  5. **Test** — CPA generates and runs tests to verify the plugin works correctly
At any point, if you have more detail and direction to give, in any format (other markdown files, PDF files, images), you can simply instruct Claude to read those files and use them in formulating the plan.
##  3\. Deploy and Test 
When you're at the point of completion where you need to try the plugin yourself, run:
    ```text
    /cpa:deploy
    ```
CPA validates the plugin, bumps the version, deploys it to your Canvas instance, and starts log monitoring so you can see output in real time.
To test our example first plugin, open a patient chart in your Canvas instance, create a new note, and click the button in the note header. You should see a Reason for Visit command appear with the text "hello world".
##  4\. Next Steps: Keep Iterating in Conversation with Claude 
The real power of AI-assisted development is iterating. Now that your plugin is deployed, enhance it with follow-up prompts:
###  Make it dynamic and patient-specific 
Ask CPA to update the plugin so the RFV text follows the classic clinical one-liner format, pulling patient data dynamically from the Canvas data module:
> Update the plugin so instead of "hello world", the RFV text is a clinical one-liner: "{patient first and last name} is a {age} year old {sex} presenting with ___". Pull the patient data dynamically.
###  Handle existing RFV 
Ask CPA to handle the case where the note already has a Reason for Visit command — editing the existing one instead of creating a duplicate:
> Update the plugin to check if the note already has a Reason for Visit command. If it does, edit the existing one instead of creating a new one.
###  Ask Claude to check logs and troubleshoot 
The `/cpa:deploy` command will start a subagent called `deploy-uat`, which runs a background task to access the `canvas logs` stream. Ask Claude to look at them anytime you need help troubleshooting. You can also of course view logs in the shell manually with:
    ```bash
    uv run canvas logs <your-instance>
    ```
###  Keep going 
Continue exploring the [SDK documentation](/sdk/) and browse other [guides](/guides/) to see what's possible with Canvas plugins. When you're ready to harden your plugin for production, run `/cpa:coverage` to check test coverage and improve tests, `/cpa:security-review` to audit for common security issues, and `/cpa:database-performance-review` to ensure efficient data retrieval. When you feel everything is ready to finalize, use the `/cpa:wrap-up` command.
----- END PAGE https://docs.canvasmedical.com/guides/your-first-plugin-with-claude-code/


----- BEGIN PAGE https://docs.canvasmedical.com/guides/your-first-plugin/
Plugins are your tool for customizing the Canvas experience. By using the modules of the Canvas SDK, you can react to [events](/sdk/events/) emitted from the EHR, request additional [data](/sdk/data/) if needed, and respond with [effects](/sdk/effects/) that alter workflows and add or change data in Canvas. You can also use [utils](/sdk/utils/) to do things like call out to web services with our provided HTTP client.
> **Warning:** This guide is for manual coding. Want a faster, AI-assisted approach? Check out [Your First Plugin (with Claude Code)](/guides/your-first-plugin-with-claude-code/), which uses an AI assistant to guide you through building and deploying your plugin. 
##  Video 
The video below showcases a Canvas engineer working through this guide step-by-step.
##  1\. Install the Canvas CLI 
To install the Canvas CLI, simply `pip install canvas`. Python 3.11–3.14 (`>=3.11, <3.15`) is required. You can find additional detail on the features of the Canvas CLI [here](/sdk/canvas_cli/).
##  2\. Configure the Canvas CLI for your instances 
The Canvas CLI uses OAuth credentials to connect to your Canvas instance. If you've used our FHIR API, you'll be very familiar with the process for [registering credentials](/api/customer-authentication/). Register a separate OAuth application, choosing `confidential` for the Client type, and `client-credentials` for the Authorization grant type. Redirect URIs can be left blank, and the Algorithm should be `No OIDC support`. Note the client_id and client_secret for the next step.
Create a file at the path `~/.canvas/credentials.ini`. Here is what its contents should look like:
    ```ini
    [buttered-popcorn]
    client_id=butter
    client_secret=salt
    [buttered-popcorn-dev]
    client_id=devbutter
    client_secret=devsalt
    is_default=true
    ```
Each section represents credentials for a different Canvas instance. Replace the section headers with your Canvas subdomains. The example configuration provided would be valid for instances with URLs `https://buttered-popcorn.canvasmedical.com` and `https://buttered-popcorn-dev.canvasmedical.com`.
You can optionally set the `is_default` flag for the instance you wish to be implied when using the CLI. If no section is set as default, the first one will be considered default.
##  3\. Initialize a new plugin 
The Canvas CLI gives you a great head start when creating a plugin. Simply run `canvas init`, and answer the prompt to name your plugin.
    ```sh
    $ canvas init
      [1/1] project_name (My Cool Plugin): Paperwork Eviscerator
    Project created in /Users/andrew/src/canvas-plugins/paperwork-eviscerator
    ```
This output shows the location of our freshly generated plugin project.
##  4\. Navigate the structure of a plugin 
Let's take a look at what was generated for us.
    ```sh
    $ tree paperwork-eviscerator/
    paperwork-eviscerator/
    ├── paperwork_eviscerator
    │    ├── CANVAS_MANIFEST.json
    │    ├── README.md
    │    └── handlers
    │         ├── __init__.py
    │         └── event_handlers.py
    ├── pyproject.toml
    └── tests
        ├── __init__.py
        └── test_models.py
    5 directories, 9 files
    ```
###  CANVAS_MANIFEST.json 
The CANVAS_MANIFEST.json is particularly important. It is used during the installation of the plugin.
    ```json
    {
        "sdk_version": "0.1.4",
        "plugin_version": "0.0.1",
        "name": "paperwork_eviscerator",
        "description": "Edit the description in CANVAS_MANIFEST.json",
        "components": {
            "handlers": [
                {
                    "class": "paperwork_eviscerator.handlers.event_handlers:Handler",
                    "description": "A handler that does xyz...",
                    "data_access": {
                        "event": "",
                        "read": [],
                        "write": []
                    }
                }
            ],
            "commands": [],
            "content": [],
            "effects": [],
            "views": []
        },
        "variables": [
            {"name": "my_secret_code", "sensitive": true}
        ],
        "tags": {},
        "references": [],
        "license": "",
        "diagram": false,
        "readme": "./README.md"
    }
    ```
The name, plugin version, and description are all surfaced in your Canvas instance when viewing installed plugins.
Only handlers declared here are invoked by the plugin runner. If they are not declared, they will be ignored.
Secrets can be declared (though not defined) here. Any secrets declared here will be initialized on plugin install, and can be set in the plugin listing in the Settings section of your Canvas instance.
###  README.md 
Share details about the purpose of your plugins and how it works in this README file.
###  handlers/event_handlers.py 
This file contains the handler class declared in the manifest file. We've included some sample content and copious comments for inspiration.
    ```python
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from logger import log
    # Inherit from BaseHandler to properly get registered for events
    class Handler(BaseHandler):
        """
        You should put a helpful description of this handler's behavior here.
        """
        # Name the event type you wish to run in response to
        RESPONDS_TO = EventType.Name(EventType.ASSESS_COMMAND__CONDITION_SELECTED)
        NARRATIVE_STRING = "I was inserted from my plugin's handler."
        def compute(self):
            """
            This method gets called when an event of the type RESPONDS_TO is fired.
            """
            # This class is initialized with several pieces of information you can
            # access.
            #
            # `self.event` is the event object that caused this method to be
            # called.
            #
            # `self.target` is an identifier for the object that is the subject of
            # the event. In this case, it would be the identifier of the assess
            # command. If this was a patient create event, it would be the
            # identifier of the patient. If this was a task update event, it would
            # be the identifier of the task. Etc, etc.
            #
            # `self.context` is a python dictionary of additional data that was
            # given with the event. The information given here depends on the
            # event type.
            #
            # `self.secrets` is a python dictionary of the secrets you defined in
            # your CANVAS_MANIFEST.json and set values for in the uploaded
            # plugin's configuration page: <emr_base_url>/admin/plugin_io/plugin/<plugin_id>/change/
            # Example: self.secrets['WEBHOOK_URL']
            # You can log things and see them using the Canvas CLI's log streaming
            # function.
            log.info(self.NARRATIVE_STRING)
            # Craft a payload to be returned with the effect(s).
            payload = {
                "note": {"uuid": self.context["note"]["uuid"]},
                "data": {"narrative": self.NARRATIVE_STRING},
            }
            # Return zero, one, or many effects.
            # Example:
            # return [Effect(type=EffectType.LOG, payload=json.dumps(payload))]
            return []
    ```
##  5\. Listen for an Event 
Set the `RESPONDS_TO` value to the [Event Type](/sdk/events/#event-types-and-context) you're interested in.
##  6\. Return an Effect 
Form an [Effect](/sdk/effects/#effect-types) to return to your Canvas instance.
##  7\. Deploy and use your plugin 
When your plugin is just the way you'd like it, deploying is simple. Navigate to the root of your plugin project (i.e. `paperwork-eviscerator/`) and run `canvas install <path/to/plugin_package>` (i.e. `canvas install paperwork_eviscerator`) and your plugin will be packaged, uploaded, installed, and enabled. As you make changes to your plugin, run the same command to update the code of the installed plugin.
##  8\. Tail the logs 
To view logs and to surface any errors with your plugin, run `canvas logs --host buttered-popcorn-dev` (replace with your Canvas instance name). This will tail the logs for all plugins installed on that instance.
----- END PAGE https://docs.canvasmedical.com/guides/your-first-plugin/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/appointment-metadata-create-form-effect/
##  Overview 
This allows developers to dynamically display additional fields when scheduling an appointment.
    ```python
    from canvas_sdk.effects.appointments_metadata import (
        FormField,
        InputType,
        AppointmentsMetadataCreateFormEffect,
    )
    AppointmentsMetadataCreateFormEffect(form_fields=[
        FormField(
            key='status',
            label='Status',
            type=InputType.SELECT,
            required=False,
            editable=True,
            options=["open", "close"],
            value=""
        ),
    ])
    ```
##  Structure 
###  **FormField**
A FormField consists of the following properties:
####  Attributes 
Attribute | Type | Description  
---|---|---  
`key` | `str` | unique identifier of the field - appointment metadata key  
`label` | `str` | the label that will be displayed on the field  
`type` | `InputType` | the type of the input - TEXT, SELECT, DATE.  
`required` | `bool` | if the input is required.  
`editable` | `bool` | if the input can be editabled.  
`options` | `list[str]` | possible options for when the input type is set to "SELECT"  
`value` | `str` | default value for the field  
###  **AppointmentsMetadataCreateFormEffect**
An AppointmentsMetadataCreateFormEffect consists of the following properties:
####  Attributes 
Attribute | Type | Description  
---|---|---  
`form_fields` | `list[FormField]` | list of fields.  
----- END PAGE https://docs.canvasmedical.com/sdk/appointment-metadata-create-form-effect/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/caching/
The Canvas SDK provides a caching API for plugin developers to store and retrieve temporary data efficiently.
For persistent storage of plugin data, use instead the [Custom Data](/sdk/custom-data/) features.
* * *
##  Getting the Cache Client 
To use the cache in your plugin, simply import and call:
    ```python
    from canvas_sdk.caching.plugins import get_cache
    cache = get_cache()
    ```
* * *
##  TTL and Expiration 
  - By default, all cached keys expire after **14 days**.
  - You can set a shorter TTL when writing to the cache via the `timeout_seconds` parameter.
  - If a longer TTL is provided, a `CachingException` will be raised.
* * *
##  Supported Methods 
###  `get(key: str, default: Any | None = None) -> Any`
Retrieve a value from the cache:
    ```python
    user = cache.get("key")
    ```
You can specify a fallback if the key doesn't exist:
    ```python
    user = cache.get("key", default="default_value")
    ```
* * *
###  `set(key: str, value: Any, timeout_seconds: int | None = None) -> None`
Store a value in the cache:
    ```python
    cache.set("key", {"name": "Alice"}, timeout_seconds=600)
    ```
* * *
###  `get_or_set(key: str, default: Any | Callable, timeout_seconds: int | None = None) -> Any`
Fetch a value or set it if not present:
    ```python
    value = cache.get_or_set("key", default=lambda: compute_value(), timeout_seconds=300)
    ```
* * *
###  `set_many(data: dict[str, Any], timeout_seconds: int | None = None) -> list[str]`
Set multiple values at once:
    ```python
    cache.set_many({
        "key1": {"name": "Alice"},
        "key2": {"name": "Bob"}
    }, timeout_seconds=900)
    ```
* * *
###  `get_many(keys: Iterable[str]) -> dict[str, Any]`
Fetch multiple values in one operation:
    ```python
    users = cache.get_many(["key1", "key2"])
    ```
* * *
###  `delete(key: str) -> None`
Remove a key from the cache:
    ```python
    cache.delete("key")
    ```
* * *
###  `__contains__(key: str) -> bool`
Check if a key exists in cache:
    ```python
    if "key" in cache:
        ...
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/caching/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/calendar-create-effect/
##  Overview 
This allows developers to create calendars for providers in Canvas. Calendars can be either Clinic or Administrative type and can optionally be associated with a location.
    ```python
    from canvas_sdk.effects.calendar import Calendar, CalendarType
    Calendar(
       provider="provider-uuid",
       type=CalendarType.Clinic,
       location="location-uuid",
       description="Primary clinic calendar"
    ).create()
    ```
##  Structure 
###  **CalendarType**
An enumeration of calendar types:
Value | Description  
---|---  
`Clinic` | Calendar for clinical appointments  
`Administrative` | Calendar for administrative tasks  
###  **Calendar**
A Calendar effect consists of the following properties:
####  Attributes 
Attribute | Type | Description  
---|---|---  
`id` | `str \| UUID \| None` | Optional unique identifier for the calendar.  
`provider` | `str \| UUID` | The provider UUID  
`type` | `CalendarType` | The type of calendar - either `CalendarType.Clinic` or `CalendarType.Administrative`  
`location` | `str \| UUID \| None` | location UUID to associate with the calendar.  
`description` | `str \| None` | description of the calendar's purpose.
----- END PAGE https://docs.canvasmedical.com/sdk/calendar-create-effect/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/calendar-event-management-effects/
##  Overview 
This allows developers to create, update, and delete calendar events for providers in Canvas. Events can be one-time or recurring, with support for daily and weekly recurrence patterns.
    ```python
    from canvas_sdk.effects.calendar import Event, EventRecurrence, DaysOfWeek
    from datetime import datetime
    # Create a one-time event
    Event(
        calendar_id="calendar-uuid",
        title="Patient Consultation",
        starts_at=datetime(2025, 1, 15, 9, 0),
        ends_at=datetime(2025, 1, 15, 10, 0)
    ).create()
    # Create a recurring event
    Event(
        calendar_id="calendar-uuid",
        title="Weekly Team Meeting",
        starts_at=datetime(2025, 1, 15, 14, 0),
        ends_at=datetime(2025, 1, 15, 15, 0),
        recurrence_frequency=EventRecurrence.Weekly,
        recurrence_interval=1,
        recurrence_days=[DaysOfWeek.Monday, DaysOfWeek.Wednesday],
        recurrence_ends_at=datetime(2025, 12, 31, 23, 59),
        allowed_note_types=["100", "101"]
    ).create()
    # Update an existing event
    Event(
        event_id="event-uuid",
        title="Updated Meeting Title",
        starts_at=datetime(2025, 1, 15, 15, 0),
        ends_at=datetime(2025, 1, 15, 16, 0)
    ).update()
    # Delete an event
    Event(event_id="event-uuid").delete()
    ```
##  Structure 
###  **EventRecurrence**
An enumeration of recurrence frequency options:
Value | Description  
---|---  
`Daily` | Event recurs daily  
`Weekly` | Event recurs weekly  
###  **DaysOfWeek**
An enumeration of days of the week for recurring events:
Value | Description  
---|---  
`MO` | Monday  
`TU` | Tuesday  
`WE` | Wednesday  
`TH` | Thursday  
`FR` | Friday  
`SA` | Saturday  
`SU` | Sunday  
###  **Event**
An Event effect consists of the following properties:
####  Attributes 
Attribute | Type | Description  
---|---|---  
`calendar_id` | `str \| UUID \| None` | The calendar UUID where the event will be created.  
`event_id` | `str \| UUID \| None` | The event UUID to update.  
`title` | `str \| None` | The title of the event.  
`starts_at` | `datetime \| None` | The start date and time of the event.  
`ends_at` | `datetime \| None` | The end date and time of the event.  
`recurrence_frequency` | `EventRecurrence \| None` | The frequency of recurrence - either `EventRecurrence.Daily` or `EventRecurrence.Weekly`.  
`recurrence_interval` | `int \| None` | The interval between recurrences (e.g., 1 for every week, 2 for every other week).  
`recurrence_days` | `list[DaysOfWeek] \| None` | List of days when the event should recur (used with weekly recurrence).  
`recurrence_ends_at` | `datetime \| None` | The date and time when the recurrence pattern ends.  
`allowed_note_types` | `list[str] \| None` | List of note types that are allowed for this event.
----- END PAGE https://docs.canvasmedical.com/sdk/calendar-event-management-effects/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/canvas_cli/
##  Getting Started 
###  Installation using `pip`
To install the Canvas CLI using `pip`, execute `pip install canvas`. Python 3.11, 3.12, or 3.13 is required.
To upgrade the Canvas CLI if you installed using `pip`, execute `pip install --upgrade canvas`.
###  Installation using `uv`
To install the Canvas CLI using `uv`, execute `uv tool install canvas`. `uv` will find or procure an acceptable Python version.
To upgrade the Canvas CLI if you installed using `uv`, execute `uv tool upgrade canvas`.
###  Configuration and Authenticating to Your Canvas Instance 
Create a file `~/.canvas/credentials.ini` with sections for each of your Canvas instance subdomains, and add client_id and client_secret credentials to each section. For example, if your Canvas instance url is `https://buttered-popcorn.canvasmedical.com/`, you would have a section `[buttered-popcorn]` with key-value pairs for `client_id` and `client_secret`.
> **Info:** **Getting Credentials:** Learn how to get register a client_id and client_secret [here](/api/customer-authentication/#registering-a-third-party-application-on-canvas).  
> The Canvas CLI uses OAuth, just like the FHIR API. 
**Example:**
    ```ini
    [buttered-popcorn]
    client_id=butter
    client_secret=salt
    [dev-buttered-popcorn]
    client_id=devbutter
    client_secret=devsalt
    is_default=true
    [localhost]
    client_id=localclientid
    client_secret=localclientsecret
    ```
You can define your default host with `is_default=true`. If no default is explicitly defined, the Canvas CLI will use the first instance in the file as the default for each of the CLI commands.
**You are now ready to use the Canvas CLI**
##  Update Notifications 
The Canvas CLI automatically checks [PyPI](https://pypi.org/project/canvas/) for newer versions. If an update is available, a notice is printed to standard error after the command output:
    ```shell
    [notice] A newer version of canvas is available (0.112.0 → 0.113.0). Upgrade with: pip install --upgrade canvas
    ```
  - The check runs at most once every 12 hours; the result is cached locally to avoid unnecessary network requests.
  - Because the notice is printed to standard error, it will not interfere with piped or redirected command output.
  - To disable update checks, set the environment variable `CANVAS_NO_UPDATE_CHECK=1`.
##  Usage 
    ```console
    $ canvas [OPTIONS] COMMAND [ARGS]...
    ```
**Options** :
  - `--version`
  - `--help`: Show this message and exit.
##  Commands 
  - `init`: Create a new plugin
  - `install`: Install a plugin into a Canvas instance
  - `uninstall`: Uninstall a plugin from a Canvas instance
  - `enable`: Enable a plugin from a Canvas instance
  - `disable`: Disable a plugin from a Canvas instance
  - `list`: List all plugins from a Canvas instance
  - `validate`: Validate a plugin's manifest and that all handlers load in the sandbox
  - `validate-manifest`: Validate the Canvas Manifest json file
  - `logs`: Listen and print log streams from a Canvas instance
  - `config list`: List plugin variables on a Canvas instance
  - `config set`: Set plugin variables on a Canvas instance
###  `canvas init`
Create a new plugin.
**Usage** :
    ```console
    $ canvas init [OPTIONS]
    ```
**Options** :
  - `--help`: Show this message and exit.
###  `canvas install`
Install a plugin into a Canvas instance.
**Usage** :
    ```console
    $ canvas install [OPTIONS] PLUGIN_NAME
    ```
**Arguments** :
  - `PLUGIN_NAME`: Path to plugin to install [required]
**Options** :
  - `--variable TEXT`: Non-sensitive variables to set, e.g. Key=value
  - `--secret TEXT`: Sensitive variables to set (treated as sensitive=true), e.g. Key=value
  - `--enable / --disable`: Install the plugin in an enabled or disabled state. Defaults to `--enable`.
  - `--host TEXT`: Canvas instance to connect to
  - `--help`: Show this message and exit.
**Notes** :
Before uploading, `canvas install` runs the same pre-flight validation as `canvas validate`:
  - Manifest validation (schema, tags, handler resolution)
  - Static lint (scans your source for sandbox-forbidden constructs and Custom Data mistakes)
  - Sandbox-load validation (imports every handler in the sandbox)
If the static lint reports an error, or any handler fails to load — for example, due to a disallowed import like `subprocess` — the install aborts before the plugin is built or uploaded, so it never reaches your instance. Run `canvas validate` first for detailed per-handler results.
The CLI automatically excludes common build artifacts from the plugin bundle:
  - `__pycache__` directories
  - `*.pyc` and `*.pyo` files
  - `node_modules` directories
  - Hidden files and directories (e.g., `.git`, `.env`)
To exclude additional files, create a `.canvasignore` file in your plugin directory. This file follows the same syntax as [.gitignore](https://git-scm.com/docs/gitignore).
Example
    ```md
    # Exclude test files
    test_*.py
    ```
###  `canvas uninstall`
Uninstall a plugin from a Canvas instance.
**Usage** :
    ```console
    $ canvas uninstall [OPTIONS] NAME
    ```
**Arguments** :
  - `NAME`: Plugin name to delete [required]
**Options** :
  - `--force`: Force uninstallation of the plugin
  - `--host TEXT`: Canvas instance to connect to
  - `--help`: Show this message and exit.
###  `canvas enable`
Enable a plugin from a Canvas instance..
**Usage** :
    ```console
    $ canvas enable [OPTIONS] NAME
    ```
**Arguments** :
  - `NAME`: Plugin name to enable [required]
**Options** :
  - `--host TEXT`: Canvas instance to connect to
  - `--help`: Show this message and exit.
###  `canvas disable`
Disable a plugin from a Canvas instance..
**Usage** :
    ```console
    $ canvas disable [OPTIONS] NAME
    ```
**Arguments** :
  - `NAME`: Plugin name to disable [required]
**Options** :
  - `--host TEXT`: Canvas instance to connect to
  - `--help`: Show this message and exit.
###  `canvas list`
List all plugins on a Canvas instance.
**Usage** :
    ```console
    $ canvas list [OPTIONS]
    ```
**Options** :
  - `--host TEXT`: Canvas instance to connect to
  - `--help`: Show this message and exit.
###  `canvas validate`
Validate a plugin's manifest and that all handlers load in the sandbox.
**Usage** :
    ```console
    $ canvas validate [OPTIONS] PLUGIN_NAME
    ```
**Arguments** :
  - `PLUGIN_NAME`: Path to plugin to validate [required]
**Options** :
  - `--help`: Show this message and exit.
This command runs full pre-flight validation combining:
  1. **Manifest validation** — Schema checks, tag validation, handler resolution, and unreferenced handler warnings (everything `validate-manifest` does).
  2. **Static lint** — Scans the plugin's source for sandbox-forbidden constructs and Custom Data mistakes before any code runs.
  3. **Sandbox-load validation** — Imports every handler the way the plugin runner will, catching violations that would otherwise surface only at runtime on the instance.
####  Static lint 
Before it loads any handlers, `canvas validate` scans every `.py` file in the plugin — skipping directories like `tests`, `build`, and `dist` _within_ the plugin — for patterns that compile cleanly but fail, or silently misbehave, once your code runs on the instance. Each finding is reported with a rule code in brackets. Warnings are printed but do not block validation; any error fails the command and exits with code 1.
    ```console
    $ canvas validate my_plugin
      ⚠ my_plugin/handlers/protocol.py:42  [custom-model-id-vs-dbid]  Widget.objects.filter(id=…) — CustomModels use `dbid` as their primary key (only core SDK models have `id`). Use `dbid=…` instead.
    These issues will fail on the instance (sandbox / Custom Data):
      ✗ my_plugin/handlers/protocol.py:18  [setattr-blocked]  `setattr()` is blocked by the sandbox. Use direct attribute assignment (`obj.attr = value`) instead.
    ```
**Sandbox constructs (errors).** These compile under RestrictedPython but are rejected when a handler runs on the instance, so a plain sandbox load can miss them. See [Sandboxing and Allowed Imports](/sdk/sandboxing-and-allowed-imports/#forbidden-constructs) for the full list and the allowed alternatives.
Rule code | Flags  
---|---  
`setattr-blocked` | `setattr(obj, "x", value)` — use `obj.x = value`  
`delattr-blocked` | `delattr(obj, "x")` — use `del obj.x`  
`bytearray-blocked` | `bytearray(...)` — use `bytes` for binary data  
`type-blocked` | Any call to `type()`. It is absent from the sandbox builtins, so even the one-argument `type(x)` raises `NameError` — use `isinstance(x, SomeClass)` or `x.__class__.__name__`, and declare classes with `class …:` rather than `type(name, bases, dict)`  
`augmented-subscript` | Augmented assignment on a subscript, e.g. `d[k] += v` — rewrite as `d[k] = d[k] + v`  
`augmented-attribute` | Augmented assignment on an attribute, e.g. `obj.attr += v` — rewrite as `obj.attr = obj.attr + v`  
`@dataclass(frozen=True)` and `@dataclass(slots=True)` load and run fine in the sandbox and are intentionally not flagged.
**Custom Data (errors).** Both leave tables silently uncreated, so queries fail at runtime. See [Custom Models](/sdk/custom-data-custom-models/) and the [Quick Start](/sdk/custom-data-quick-start/) for setup.
Rule code | Flags  
---|---  
`custom-model-wrong-dir` | A `CustomModel` subclass defined outside `<plugin>/models/` — Canvas only loads models from that directory  
`missing-custom-data-block` | CustomModels are present but the manifest has no `custom_data` block (an empty block counts as missing — it must be non-empty)  
**Custom Data (warnings).** These don't block validation but usually indicate a bug:
Rule code | Flags  
---|---  
`custom-model-id-vs-dbid` | `.filter(id=…)` / `.get(id=…)` on a local CustomModel — CustomModels key on `dbid`, not `id`. Use `dbid=…`  
`lazy-fk-string-ref` | A `ForeignKey`/`OneToOneField`/`ManyToManyField` with a string reference to a CustomModel defined in this plugin — import the class and pass it directly  
####  Sandbox-load validation 
After the static lint passes, sandbox-load validation executes each handler module in the plugin sandbox to catch:
  - **Disallowed imports** — Modules like `subprocess`, `socket`, or `os` that are blocked by the sandbox.
  - **RestrictedPython compile-time errors** — Syntax or constructs that RestrictedPython cannot compile.
  - **Import errors** — Missing dependencies or broken imports.
For each handler, the output shows whether it loaded successfully:
    ```console
    $ canvas validate my_plugin
    Loading 2 handler(s) in the sandbox:
      ✓ my_plugin.handlers.events:MyHandler
      ✗ my_plugin.handlers.api:APIHandler
        ImportError: 'subprocess' is not an allowed import
    1 of 2 handler(s) failed to load in the sandbox.
    ```
The command exits with code 1 if any handler fails validation.
####  Limitations 
A passing `canvas validate` confirms that handlers import cleanly under the sandbox — it does not guarantee the plugin is fully sandbox-clean. RestrictedPython checks attribute and item access inside `compute()` at request time, not at import time, so violations during handler execution won't be caught by this command.
> **Info:** `canvas install` runs this same static lint and sandbox-load validation before uploading, so violations are caught before they reach your instance. 
###  `canvas validate-manifest`
Validate the Canvas Manifest json file.
**Usage** :
    ```console
    $ canvas validate-manifest [OPTIONS] PLUGIN_NAME
    ```
**Arguments** :
  - `PLUGIN_NAME`: Path to plugin to validate [required]
**Options** :
  - `--help`: Show this message and exit.
**Validations performed** :
  1. **Schema validation** — Checks that `CANVAS_MANIFEST.json` contains all required fields and valid values.
  2. **Handler resolution** — Verifies that every handler class declared in the manifest (`protocols`, `applications`, and `handlers`) resolves to a file the plugin runner can find at runtime.
####  Handler resolution and directory layout 
The plugin runner loads handlers by mapping dotted module paths to files relative to the plugin's install directory. For a plugin named `my_plugin` with a handler class `my_plugin.handlers.events:MyHandler`, the runner expects `handlers/events.py` inside the plugin directory — the directory containing `CANVAS_MANIFEST.json`.
A common mistake is placing `CANVAS_MANIFEST.json` in a parent directory above the plugin package. This passes schema validation and works locally, but fails at runtime with `ModuleNotFoundError` — the handler files are nested one level too deep.
**Correct layout:**
    ```text
    my_plugin/
    ├── CANVAS_MANIFEST.json   # ← manifest inside the package
    ├── handlers/
    │   └── events.py
    └── ...
    ```
**Incorrect layout:**
    ```text
    project/
    ├── CANVAS_MANIFEST.json   # ← manifest above the package (wrong!)
    └── my_plugin/
        └── handlers/
            └── events.py
    ```
If `validate-manifest` detects handlers that won't resolve, it reports which classes are affected and the file paths the runner expects:
    ```console
    Error: these handler classes won't be found by the plugin runner with the current directory layout:
      - my_plugin.handlers.events:MyHandler
        runner expects: my_plugin/handlers/events.py
    CANVAS_MANIFEST.json must live inside the plugin's package directory (the directory whose name matches the manifest "name"), alongside the handler packages — not in a parent directory above them.
    ```
> **Info:** `canvas install` runs manifest validation, the static lint, and sandbox-load validation before uploading. Use `canvas validate` for a full pre-flight check with detailed per-handler output. 
###  `canvas logs`
Subscribes to a log stream and prints to your console. Optionally fetches historical logs first.
**Usage** :
    ```console
    $ canvas logs [OPTIONS]
    ```
**Options** :
  - `--host TEXT`: Canvas instance to connect to
  - `--help`: Show this message and exit.
  - `--since TEXT`: Lookback window (e.g. '24h', '2h30m'). Mutually exclusive with –start/–end.
  - `--start TEXT`: Start time (ISO/RFC3339) or 'now'.
  - `--end TEXT`: End time (ISO/RFC3339) or 'now'. Defaults to now if start is provided.
  - `--no-follow`: Historical only; do not stream live logs.
  - `--level TEXT`: Repeatable. –level ERROR –level WARN
  - `--source TEXT`: Filter by source/service.
  - `--plugin TEXT`: Repeatable. –plugin foo –plugin bar.
  - `--handler TEXT`: Repeatable. Qualified handler name (e.g. my_plugin.handlers.Foo).
  - `--page-size INTEGER`: Fetch size per page (historical). [default: 200]
  - `--limit INTEGER`: Max historical logs to print.
  - `--all`: Fetch all pages until exhausted (historical).
  - `--interactive`: After each page, prompt to load more.
  - `--cursor TEXT`: Resume token from a previous run.
  - `--help`: Show this message and exit.
###  `canvas config list`
List plugin variables on a Canvas instance. Each variable is rendered as `[set]` or `[not set]`, with a `(sensitive)` annotation for sensitive variables. Values themselves are never displayed — to read a value, use the Django Admin UI (gated by managing-user permissions).
**Usage** :
    ```console
    $ canvas config list [OPTIONS] PLUGIN
    ```
**Example output** :
    ```console
    $ canvas config list my_plugin
      API_TOKEN  [set]  (sensitive)
      LOG_LEVEL  [not set]
    ```
**Arguments** :
  - `PLUGIN`: Plugin name to list variables for
**Options** :
  - `--host TEXT`: Canvas instance to connect to
  - `--help`: Show this message and exit.
**Example Output** :
    ```console
    $ canvas config list my_plugin
      API_TOKEN = [set]  (sensitive)
      WEBHOOK_URL = [set]
      DEBUG_MODE = [not set]
    ```
###  `canvas config set`
Set (or update) one or more plugin variables on a Canvas instance. Each variable must already be declared in the plugin's `CANVAS_MANIFEST.json`. Pass one or more `KEY=value` pairs as positional arguments.
**Usage** :
    ```console
    $ canvas config set [OPTIONS] PLUGIN VARIABLES...
    ```
**Examples** :
Set a single variable:
    ```console
    $ canvas config set my_plugin API_TOKEN=your_api_token_value
    ```
Set multiple variables in one call:
    ```console
    $ canvas config set my_plugin API_TOKEN=abc123 LOG_LEVEL=info
    ```
Set a variable whose value is a list with one entry per line — for example a redirect allowlist (see the [Redirect effect](/sdk/effect-redirect/)). The value is newline-delimited (not comma-separated), so use your shell's newline quoting to preserve the line breaks. In bash/zsh, ANSI-C quoting (`$'…'`) turns `\n` into a real newline:
    ```console
    $ canvas config set my_plugin $'REDIRECT_ALLOWLIST_INTERNAL=/panel\n/patient'
    ```
**Arguments** :
  - `PLUGIN`: Plugin name to set variables for
  - `VARIABLES...`: Variables to set, e.g. Key=value
**Options** :
  - `--host TEXT`: Canvas instance to connect to
  - `--help`: Show this message and exit.
> Whether each value is treated as sensitive is determined by the plugin's `CANVAS_MANIFEST.json` (`variables: [{name, sensitive}]`) — `canvas config set` does not change the sensitive flag.
----- END PAGE https://docs.canvasmedical.com/sdk/canvas_cli/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/clients-aws-s3/
The Canvas SDK AWS S3 client provides a simple interface for interacting with Amazon S3 storage, including uploading, downloading, listing, and deleting objects, as well as generating presigned URLs for temporary access.
##  Requirements 
  - **AWS Access Key ID** : Your AWS access key
  - **AWS Secret Access Key** : Your AWS secret key
  - **AWS Region** : The region where your bucket is located (e.g., `us-east-1`)
  - **S3 Bucket Name** : The name of your S3 bucket
##  Imports 
The AWS S3 client is included in the Canvas SDK. Import the necessary components:
    ```python
    from canvas_sdk.clients.aws import S3, Credentials, S3Item
    ```
Or import from specific modules:
    ```python
    from canvas_sdk.clients.aws.libraries import S3
    from canvas_sdk.clients.aws.structures import Credentials, S3Item
    ```
##  Initialize the Client 
    ```python
    from canvas_sdk.clients.aws import S3, Credentials
    credentials = Credentials(
        key="your_aws_access_key_id",
        secret="your_aws_secret_access_key",
        region="us-east-1",
        bucket="your-bucket-name"
    )
    client = S3(credentials)
    ```
##  Check if Client is Ready 
    ```python
    if client.is_ready():
        print("S3 client is configured and ready")
    else:
        print("Missing credentials")
    ```
##  Upload a Text File 
    ```python
    from canvas_sdk.clients.aws import S3, Credentials
    credentials = Credentials(
        key="your_access_key",
        secret="your_secret_key",
        region="us-east-1",
        bucket="my-bucket"
    )
    client = S3(credentials)
    # Upload text content
    response = client.upload_text_to_s3("path/to/file.txt", "Hello, World!")
    if response and response.status_code == 200:
        print("Text file uploaded successfully!")
    ```
##  Upload a Binary File 
    ```python
    # Upload binary content (e.g., an image)
    with open("local_image.png", "rb") as f:
        binary_data = f.read()
    response = client.upload_binary_to_s3(
        "images/uploaded_image.png",
        binary_data,
        "image/png"
    )
    if response and response.status_code == 200:
        print("Binary file uploaded successfully!")
    ```
##  Download a File 
    ```python
    response = client.access_s3_object("path/to/file.txt")
    if response:
        content = response.content
        print(f"Downloaded content: {content.decode('utf-8')}")
    ```
##  List Objects in Bucket 
    ```python
    # List all objects with a prefix
    items = client.list_s3_objects("documents/")
    if items:
        for item in items:
            print(f"Key: {item.key}, Size: {item.size} bytes, Modified: {item.last_modified}")
    ```
##  Delete an Object 
    ```python
    response = client.delete_object("path/to/file.txt")
    if response and response.status_code == 204:
        print("Object deleted successfully!")
    ```
##  Generate a Presigned URL 
    ```python
    # Generate a URL valid for 1 hour (3600 seconds)
    url = client.generate_presigned_url("path/to/file.txt", expiration=3600)
    if url:
        print(f"Presigned URL: {url}")
    ```
##  S3 
The main class for interacting with AWS S3.
###  Constructor 
    ```python
    S3(credentials: Credentials)
    ```
Parameter | Type | Description  
---|---|---  
`credentials` | `Credentials` | AWS credentials for S3 access  
###  Methods 
####  `is_ready() -> bool`
Check if all required credentials are provided.
**Returns:** `True` if all credentials (key, secret, region, bucket) are non-empty, `False` otherwise.
####  `access_s3_object(object_key: str) -> Response | None`
Download an object from S3.
**Parameters:**
Parameter | Type | Description  
---|---|---  
`object_key` | `str` | S3 object key (path) to access  
**Returns:** `requests.Response` containing the object data, or `None` if credentials are not ready.
####  `upload_text_to_s3(object_key: str, data: str) -> Response | None`
Upload text data to S3 as `text/plain`.
**Parameters:**
Parameter | Type | Description  
---|---|---  
`object_key` | `str` | S3 object key (path) to create/update  
`data` | `str` | Text content to upload  
**Returns:** `requests.Response` from S3, or `None` if credentials are not ready.
####  `upload_binary_to_s3(object_key: str, binary_data: bytes, content_type: str) -> Response | None`
Upload binary data to S3.
**Parameters:**
Parameter | Type | Description  
---|---|---  
`object_key` | `str` | S3 object key (path) to create/update  
`binary_data` | `bytes` | Binary content to upload  
`content_type` | `str` | MIME type (e.g., `image/png`)  
**Returns:** `requests.Response` from S3, or `None` if credentials are not ready.
####  `delete_object(object_key: str) -> Response | None`
Delete an object from S3.
**Parameters:**
Parameter | Type | Description  
---|---|---  
`object_key` | `str` | S3 object key (path) to delete  
**Returns:** `requests.Response` from S3, or `None` if credentials are not ready.
####  `list_s3_objects(prefix: str) -> list[S3Item] | None`
List all objects in S3 with the given prefix. Handles pagination automatically.
**Parameters:**
Parameter | Type | Description  
---|---|---  
`prefix` | `str` | S3 key prefix to filter objects  
**Returns:** List of `S3Item` objects with metadata, or `None` if credentials are not ready.
**Raises:** `Exception` if S3 returns a non-200 status code.
####  `generate_presigned_url(object_key: str, expiration: int) -> str | None`
Generate a presigned URL for temporary access to an S3 object.
**Parameters:**
Parameter | Type | Description  
---|---|---  
`object_key` | `str` | S3 object key (path)  
`expiration` | `int` | URL expiration time in seconds  
**Returns:** Presigned URL string, or `None` if credentials are not ready.
##  Data Structures 
###  Credentials 
AWS credentials for S3 access.
Field | Type | Description  
---|---|---  
`key` | `str` | AWS access key ID  
`secret` | `str` | AWS secret access key  
`region` | `str` | AWS region (e.g., `us-east-1`)  
`bucket` | `str` | S3 bucket name  
**Example:**
    ```python
    from canvas_sdk.clients.aws import Credentials
    credentials = Credentials(
        key="AKIAIOSFODNN7EXAMPLE",
        secret="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
        region="us-west-2",
        bucket="my-application-bucket"
    )
    ```
###  S3Item 
S3 object metadata returned by `list_s3_objects`.
Field | Type | Description  
---|---|---  
`key` | `str` | Object key (path) in the S3 bucket  
`size` | `int` | Object size in bytes  
`last_modified` | `datetime` | Timestamp of the last modification  
**Example:**
    ```python
    items = client.list_s3_objects("documents/")
    for item in items:
        print(f"File: {item.key}")
        print(f"Size: {item.size} bytes")
        print(f"Last Modified: {item.last_modified}")
    ```
##  Complete Plugin Example 
Here's a complete example of using the S3 client in a Canvas plugin:
    ```python
    from http import HTTPStatus
    from canvas_sdk.clients.aws import S3, Credentials
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.simple_api import JSONResponse, PlainTextResponse, Response
    from canvas_sdk.handlers.simple_api import Credentials as APICredentials, SimpleAPI, api
    class S3Handler(SimpleAPI):
        """Simple API handler for S3 operations."""
        def authenticate(self, credentials: APICredentials) -> bool:
            return True
        def _s3_client(self) -> S3:
            """Create S3 client from plugin secrets."""
            return S3(
                Credentials(
                    key=self.secrets["S3Key"],
                    secret=self.secrets["S3Secret"],
                    region=self.secrets["S3Region"],
                    bucket=self.secrets["S3Bucket"],
                )
            )
        @api.get("/list")
        def list_files(self) -> list[Response | Effect]:
            """List all files in the bucket."""
            client = self._s3_client()
            if client.is_ready():
                items = client.list_s3_objects("")
                content = [{"key": p.key, "size": p.size} for p in items]
                return [JSONResponse(content, status_code=HTTPStatus.OK)]
            return []
        @api.get("/download/<file_key>")
        def download_file(self) -> list[Response | Effect]:
            """Download a file by key."""
            file_key = self.request.path_params["file_key"]
            client = self._s3_client()
            if client.is_ready() and file_key:
                response = client.access_s3_object(file_key)
                return [Response(response.content, status_code=HTTPStatus.OK)]
            return []
        @api.post("/upload/<file_key>")
        def upload_file(self) -> list[Response | Effect]:
            """Upload a file."""
            file_key = self.request.path_params["file_key"]
            client = self._s3_client()
            content = self.request.body
            content_type = self.request.content_type
            if client.is_ready() and file_key:
                if content_type == "text/plain":
                    response = client.upload_text_to_s3(file_key, content.decode("utf-8"))
                else:
                    response = client.upload_binary_to_s3(file_key, content, content_type)
                return [Response(response.content, status_code=response.status_code)]
            return []
        @api.delete("/delete/<file_key>")
        def delete_file(self) -> list[Response | Effect]:
            """Delete a file by key."""
            file_key = self.request.path_params["file_key"]
            client = self._s3_client()
            if client.is_ready() and file_key:
                response = client.delete_object(file_key)
                return [Response(response.content, status_code=HTTPStatus.OK)]
            return []
        @api.get("/presigned/<file_key>")
        def get_presigned_url(self) -> list[Response | Effect]:
            """Generate a presigned URL for temporary access."""
            file_key = self.request.path_params["file_key"]
            client = self._s3_client()
            if client.is_ready() and file_key:
                url = client.generate_presigned_url(file_key, 3600)  # 1 hour
                return [PlainTextResponse(url, status_code=HTTPStatus.OK)]
            return []
    ```
##  Error Handling 
The S3 client methods return `None` when credentials are not ready. For list operations, an `Exception` is raised if S3 returns an error status code.
    ```python
    # Check credentials before operations
    if not client.is_ready():
        print("S3 credentials are not configured")
        return
    # Handle list errors
    try:
        items = client.list_s3_objects("prefix/")
    except Exception as e:
        print(f"S3 error: {e}")
    # Check response status for uploads/downloads
    response = client.upload_text_to_s3("file.txt", "content")
    if response:
        if response.status_code == 200:
            print("Upload successful")
        else:
            print(f"Upload failed with status {response.status_code}")
    else:
        print("Credentials not ready")
    ```
##  AWS Signature V4 Authentication 
The S3 client implements AWS Signature Version 4 for request authentication. This is handled automatically - you only need to provide valid credentials. The client:
  - Signs all requests with HMAC-SHA256
  - Generates proper canonical requests
  - Handles date/time formatting for AWS
  - Supports presigned URLs for temporary access
##  Additional Resources 
  - [AWS S3 Documentation](https://docs.aws.amazon.com/s3/)
  - [AWS Signature Version 4](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html)
  - [S3 REST API Reference](https://docs.aws.amazon.com/AmazonS3/latest/API/Welcome.html)
  - [Example Plugin](/sdk/example-aws_s3/) \- Documentation for the example plugin
  - [Source Code](https://github.com/canvas-medical/canvas-plugins/tree/main/example-plugins/aws_s3) \- View the source on GitHub
----- END PAGE https://docs.canvasmedical.com/sdk/clients-aws-s3/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/clients-canvas-fhir/
The Canvas SDK FHIR client provides a simple interface for interacting with the [Canvas FHIR API](/api/), supporting CRUD operations on FHIR resources such as Coverages, DocumentReferences, AllergyIntolerances, and more. It handles OAuth client credentials authentication and token caching automatically.
##  Requirements 
  - **Canvas FHIR Client ID** : An OAuth client ID for your Canvas environment
  - **Canvas FHIR Client Secret** : The corresponding OAuth client secret
These credentials should be stored as [plugin secrets](/sdk/secrets/) and grant access to the Canvas FHIR API for your environment.
##  Imports 
The Canvas FHIR client is included in the Canvas SDK. Import the client:
    ```python
    from canvas_sdk.clients.canvas_fhir import CanvasFhir
    ```
##  Initialize the Client 
    ```python
    # Declare these secrets in the CANVAS_MANIFEST.json and set the values on the
    # plugin configuration page.
    client_id = self.secrets["CANVAS_FHIR_CLIENT_ID"]
    client_secret = self.secrets["CANVAS_FHIR_CLIENT_SECRET"]
    client = CanvasFhir(client_id, client_secret)
    ```
On initialization, the client will:
  1. Authenticate using the OAuth client credentials flow against your Canvas environment's token endpoint.
  2. Cache the access token using the plugin cache system, keyed by `client_id`, with automatic expiration.
  3. Determine the FHIR API base URL from the environment's `CUSTOMER_IDENTIFIER` setting (e.g., `https://fumage-{CUSTOMER_IDENTIFIER}.canvasmedical.com`).
##  CanvasFhir 
The main class for interacting with the Canvas FHIR API.
###  Constructor 
    ```python
    CanvasFhir(client_id: str, client_secret: str)
    ```
Parameter | Type | Description  
---|---|---  
`client_id` | `str` | OAuth client ID for the Canvas API  
`client_secret` | `str` | OAuth client secret  
###  Methods 
####  `search(resource_type: str, parameters: dict) -> dict`
Search for FHIR resources matching the given parameters.
    ```python
    # Search for a patient's allergy intolerances
    results = client.search("AllergyIntolerance", {"patient": "Patient/abc123"})
    for entry in results.get("entry", []):
        resource = entry["resource"]
        print(f"Allergy: {resource['code']['coding'][0]['display']}")
    ```
Parameter | Type | Description  
---|---|---  
`resource_type` | `str` | FHIR resource type (e.g., `Patient`, `Coverage`)  
`parameters` | `dict` | Search parameters as key-value pairs  
**Returns:** FHIR Bundle `dict` containing matching resources.
**Raises:** `requests.HTTPError` if the API returns an error status code.
####  `read(resource_type: str, resource_id: str) -> dict`
Read a single FHIR resource by its ID.
    ```python
    # Read a specific resource by ID
    allergy = client.read("AllergyIntolerance", "allergy-id-123")
    print(f"Status: {allergy['clinicalStatus']['coding'][0]['code']}")
    ```
Parameter | Type | Description  
---|---|---  
`resource_type` | `str` | FHIR resource type  
`resource_id` | `str` | ID of the resource to read  
**Returns:** FHIR resource `dict`.
**Raises:** `requests.HTTPError` if the API returns an error status code.
####  `create(resource_type: str, data: dict) -> dict`
Create a new FHIR resource.
    ```python
    # Create a new Coverage resource
    coverage = client.create("Coverage", {
        "resourceType": "Coverage",
        "status": "active",
        "beneficiary": {"reference": "Patient/abc123"},
        "payor": [{"reference": "Organization/org-456"}],
    })
    print(f"Created Coverage: {coverage['id']}")
    ```
Parameter | Type | Description  
---|---|---  
`resource_type` | `str` | FHIR resource type  
`data` | `dict` | FHIR resource data to create  
**Returns:** Created FHIR resource `dict` (including server-assigned `id`).
**Raises:** `requests.HTTPError` if the API returns an error status code.
####  `update(resource_type: str, resource_id: str, data: dict) -> dict`
Update an existing FHIR resource.
    ```python
    # Update an existing resource
    updated = client.update("Coverage", "coverage-id-789", {
        "resourceType": "Coverage",
        "id": "coverage-id-789",
        "status": "cancelled",
        "beneficiary": {"reference": "Patient/abc123"},
        "payor": [{"reference": "Organization/org-456"}],
    })
    print(f"Updated Coverage status: {updated['status']}")
    ```
Parameter | Type | Description  
---|---|---  
`resource_type` | `str` | FHIR resource type  
`resource_id` | `str` | ID of the resource to update  
`data` | `dict` | Complete FHIR resource data  
**Returns:** Updated FHIR resource `dict`.
**Raises:** `requests.HTTPError` if the API returns an error status code.
##  Authentication 
The client uses the OAuth 2.0 client credentials flow to authenticate with the Canvas API. Token management is handled automatically:
  - On first use, the client exchanges the `client_id` and `client_secret` for an access token via the Canvas token endpoint.
  - The token is cached using the plugin cache system with the key `canvas_fhir_credentials_{client_id}`.
  - The cached token expires 60 seconds before the actual token expiration to avoid using stale credentials.
  - Subsequent requests reuse the cached token until it expires.
##  Error Handling 
The Canvas FHIR client uses `raise_for_status()` on all HTTP responses, which raises `requests.HTTPError` for non-successful status codes.
    ```python
    from requests import HTTPError
    try:
        result = client.read("Patient", "nonexistent-id")
    except HTTPError as e:
        print(f"HTTP {e.response.status_code}: {e.response.text}")
    ```
##  Complete Plugin Example 
Here's a complete example of using the Canvas FHIR client in an ActionButton handler:
    ```python
    from canvas_sdk.clients.canvas_fhir import CanvasFhir
    from canvas_sdk.effects import Effect
    from canvas_sdk.handlers.action_button import ActionButton
    from logger import log
    class FhirRequestHandler(ActionButton):
        """Handler that queries the FHIR API when a button is clicked."""
        BUTTON_TITLE = "Trigger FHIR Request"
        BUTTON_KEY = "TRIGGER_FHIR_REQUEST"
        BUTTON_LOCATION = ActionButton.ButtonLocation.CHART_SUMMARY_ALLERGIES_SECTION
        def handle(self) -> list[Effect]:
            """Handle the button click."""
            client_id = self.secrets["CANVAS_FHIR_CLIENT_ID"]
            client_secret = self.secrets["CANVAS_FHIR_CLIENT_SECRET"]
            patient_id = self.event.target.id
            client = CanvasFhir(client_id, client_secret)
            # Search for the patient's allergy intolerances
            search_response = client.search(
                "AllergyIntolerance",
                {"patient": f"Patient/{patient_id}"},
            )
            log.info(f"Search: {search_response}")
            # Read the first result
            first_entry = search_response["entry"][0]["resource"]
            read_response = client.read("AllergyIntolerance", first_entry["id"])
            log.info(f"Read: {read_response}")
            return []
    ```
##  Additional Resources 
  - [Canvas FHIR API Documentation](/api/)
  - [Example Plugin Source Code](https://github.com/canvas-medical/canvas-plugins/tree/main/example-plugins/canvas_fhir_client)
----- END PAGE https://docs.canvasmedical.com/sdk/clients-canvas-fhir/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/clients-extend-ai/
The Canvas SDK Extend AI client provides an interface for document processing using AI-powered extraction, classification, and splitting capabilities through the Extend AI API.
##  Requirements 
  - **Extend AI API Key** : Obtain from your [Extend AI dashboard](https://app.extend.ai/)
##  What is Extend AI? 
Extend AI provides intelligent document processing (IDP) capabilities:
  - **Extraction** : Extract structured data from documents based on a defined schema
  - **Classification** : Classify documents into predefined categories
  - **Splitting** : Split multi-page documents into logical sections
##  Imports 
The Extend AI client is included in the Canvas SDK. Import the necessary components:
    ```python
    from canvas_sdk.clients.extend_ai.libraries import Client
    from canvas_sdk.clients.extend_ai.constants import RunStatus, VersionName
    from canvas_sdk.clients.extend_ai.structures import RequestFailed
    ```
##  Initialize the Client 
    ```python
    client = Client(key="your_extend_ai_api_key")
    ```
##  Extract Data from a Document (Using an Existing Processor) 
The most common use case is running an existing processor on a document. Here's a complete example:
    ```python
    import time
    from canvas_sdk.clients.extend_ai.libraries import Client
    from canvas_sdk.clients.extend_ai.constants import RunStatus
    from canvas_sdk.clients.extend_ai.structures import RequestFailed
    # Initialize the client
    client = Client(key="your_api_key")
    # Your processor ID (created in the Extend AI dashboard)
    processor_id = "proc_xxxxxxxxxxxxxxxxx"
    # URL to the document (must be publicly accessible)
    document_url = "https://your-bucket.s3.amazonaws.com/document.pdf"
    try:
        # Start the processor run
        run = client.run_processor(
            processor_id=processor_id,
            file_name="my-document.pdf",
            file_url=document_url,
            config=None,  # Use processor's default configuration
        )
        print(f"Run started! ID: {run.id}, Status: {run.status.value}")
        # Poll for completion
        while run.status in (RunStatus.PENDING, RunStatus.PROCESSING):
            time.sleep(2)  # Wait 2 seconds between checks
            run = client.run_status(run.id)
            print(f"Status: {run.status.value}")
        # Check result
        if run.status == RunStatus.PROCESSED:
            print("Extraction successful!")
            print(f"Extracted data: {run.output.value}")
        else:
            print(f"Processing failed with status: {run.status.value}")
    except RequestFailed as e:
        print(f"Error: {e.message} (HTTP {e.status_code})")
    ```
##  List Available Processors 
    ```python
    # List all processors in your account
    for processor in client.list_processors():
        print(f"ID: {processor.id}")
        print(f"Name: {processor.name}")
        print(f"Type: {processor.type.value}")
        print("---")
    ```
##  Get Processor Configuration 
    ```python
    from canvas_sdk.clients.extend_ai.constants import VersionName
    # Get the draft version of a processor
    processor_version = client.processor(
        processor_id="proc_xxxxxxxxxxxxxxxxx",
        version=VersionName.DRAFT.value
    )
    print(f"Processor: {processor_version.processor.name}")
    print(f"Version: {processor_version.version}")
    print(f"Type: {processor_version.processor.type.value}")
    # Access the schema (for extraction processors)
    if hasattr(processor_version.config, 'schema'):
        print(f"Schema: {processor_version.config.schema}")
    ```
##  Check Run Status and Get Results 
    ```python
    # Check the status of a run
    run = client.run_status("run_xxxxxxxxxxxxxxxxx")
    print(f"Status: {run.status.value}")
    print(f"Credits used: {run.usage}")
    if run.status == RunStatus.PROCESSED:
        # For extraction processors
        if hasattr(run.output, 'value'):
            extracted_data = run.output.value
            print(f"Extracted: {extracted_data}")
        # For classification processors
        if hasattr(run.output, 'type'):
            print(f"Classification: {run.output.type}")
            print(f"Confidence: {run.output.confidence}")
        # For splitter processors
        if hasattr(run.output, 'splits'):
            for split in run.output.splits:
                print(f"Split: {split.type}, Pages {split.startPage}-{split.endPage}")
    ```
##  Clean Up Files After Processing 
    ```python
    # After processing, delete the uploaded files to save storage
    run = client.run_status("run_xxxxxxxxxxxxxxxxx")
    if run.status == RunStatus.PROCESSED:
        for file in run.files:
            deleted = client.delete_file(file.id)
            print(f"Deleted file {file.name}: {deleted}")
    ```
##  Complete Workflow Example 
    ```python
    import time
    from canvas_sdk.clients.extend_ai.libraries import Client
    from canvas_sdk.clients.extend_ai.constants import RunStatus
    from canvas_sdk.clients.extend_ai.structures import RequestFailed
    def extract_from_document(api_key: str, processor_id: str, document_url: str) -> dict:
        """
        Extract structured data from a document using Extend AI.
        Args:
            api_key: Your Extend AI API key
            processor_id: The processor ID to use
            document_url: Public URL to the document
        Returns:
            Dictionary containing the extracted data
        Raises:
            RequestFailed: If the API request fails
            RuntimeError: If processing fails or times out
        """
        client = Client(key=api_key)
        # Start processing
        run = client.run_processor(
            processor_id=processor_id,
            file_name="document.pdf",
            file_url=document_url,
            config=None,
        )
        # Wait for completion (with timeout)
        max_attempts = 30  # 60 seconds max
        attempts = 0
        while run.status in (RunStatus.PENDING, RunStatus.PROCESSING):
            if attempts >= max_attempts:
                raise RuntimeError("Processing timed out")
            time.sleep(2)
            run = client.run_status(run.id)
            attempts += 1
        # Handle result
        if run.status == RunStatus.PROCESSED:
            # Clean up files
            for file in run.files:
                client.delete_file(file.id)
            return run.output.value if hasattr(run.output, 'value') else run.output.to_dict()
        raise RuntimeError(f"Processing failed: {run.status.value}")
    # Usage
    result = extract_from_document(
        api_key="your_api_key",
        processor_id="proc_xxxxxxxxxxxxxxxxx",
        document_url="https://example.com/document.pdf"
    )
    print(result)
    ```
##  Client 
The main class for interacting with the Extend AI API.
###  Constructor 
    ```python
    Client(key: str)
    ```
Parameter | Type | Description  
---|---|---  
`key` | `str` | Extend AI API key  
###  File Management 
####  `list_files() -> Iterator[StoredFile]`
List all files stored in Extend AI.
    ```python
    for file in client.list_files():
        print(f"{file.id}: {file.name} ({file.type})")
    ```
**Returns:** Iterator of `StoredFile` objects
**Raises:** `RequestFailed` on error
####  `delete_file(file_id: str) -> bool`
Delete a file from Extend AI storage.
    ```python
    deleted = client.delete_file("file_xxxxxxxxxxxxxxxxx")
    print(f"Deleted: {deleted}")
    ```
Parameter | Type | Description  
---|---|---  
`file_id` | `str` | Unique identifier of the file  
**Returns:** `True` on success
**Raises:** `RequestFailed` on error
###  Processor Management 
####  `list_processors() -> Iterator[ProcessorMeta]`
List all processors in the account.
    ```python
    for processor in client.list_processors():
        print(f"{processor.name}: {processor.type.value}")
    ```
**Returns:** Iterator of `ProcessorMeta` objects
**Raises:** `RequestFailed` on error
####  `processor(processor_id: str, version: str) -> ProcessorVersion`
Get details for a specific processor version.
    ```python
    from canvas_sdk.clients.extend_ai.constants import VersionName
    # Get draft version
    processor = client.processor("proc_xxx", VersionName.DRAFT.value)
    # Get latest published version
    processor = client.processor("proc_xxx", VersionName.LATEST.value)
    # Get specific version
    processor = client.processor("proc_xxx", "v1")
    ```
Parameter | Type | Description  
---|---|---  
`processor_id` | `str` | Unique identifier of the processor  
`version` | `str` | Version name (`draft`, `latest`, or `vN`)  
**Returns:** `ProcessorVersion` object
**Raises:** `RequestFailed` on error
####  `create_processor(name: str, config: ConfigBase) -> ProcessorMeta`
Create a new processor with the specified configuration.
    ```python
    from canvas_sdk.clients.extend_ai.constants import BaseProcessor
    from canvas_sdk.clients.extend_ai.structures.config import (
        ConfigExtraction,
        AdvancedOptionsExtraction,
        Parser,
    )
    config = ConfigExtraction(
        base_processor=BaseProcessor.EXTRACTION_PERFORMANCE,
        extraction_rule="Extract all relevant fields",
        schema={
            "type": "object",
            "properties": {
                "name": {"type": "string"},
                "date": {"type": "string"},
                "amount": {"type": "number"},
            }
        },
        advanced_options=AdvancedOptionsExtraction.from_dict({}),
        parser=Parser.from_dict({}),
    )
    processor = client.create_processor("Invoice Extractor", config)
    print(f"Created: {processor.id}")
    ```
Parameter | Type | Description  
---|---|---  
`name` | `str` | Name for the new processor  
`config` | `ConfigBase` | Processor configuration object  
**Returns:** `ProcessorMeta` object
**Raises:** `RequestFailed` on error
###  Running Processors 
####  `run_processor(processor_id, file_name, file_url, config) -> ProcessorRun`
Execute a processor on a document.
    ```python
    run = client.run_processor(
        processor_id="proc_xxxxxxxxxxxxxxxxx",
        file_name="invoice.pdf",
        file_url="https://bucket.s3.amazonaws.com/invoice.pdf",
        config=None,  # Use processor defaults
    )
    print(f"Run ID: {run.id}, Status: {run.status.value}")
    ```
Parameter | Type | Description  
---|---|---  
`processor_id` | `str` | Processor to run  
`file_name` | `str` | Name for the file  
`file_url` | `str` | Public URL to the document  
`config` | `ConfigExtraction \| None` | Optional config override (extraction only)  
**Returns:** `ProcessorRun` object with initial status
**Raises:** `RequestFailed` on error
####  `run_status(run_id: str) -> ProcessorRun`
Get the current status and results of a processor run.
    ```python
    run = client.run_status("run_xxxxxxxxxxxxxxxxx")
    if run.status == RunStatus.PROCESSED:
        print(f"Result: {run.output.to_dict()}")
    elif run.status == RunStatus.FAILED:
        print("Processing failed")
    else:
        print(f"Still processing: {run.status.value}")
    ```
Parameter | Type | Description  
---|---|---  
`run_id` | `str` | Unique identifier of the run  
**Returns:** `ProcessorRun` object with current status and results
**Raises:** `RequestFailed` on error
##  Data Structures 
###  ProcessorMeta 
Metadata about a processor.
Field | Type | Description  
---|---|---  
`id` | `str` | Unique processor identifier  
`name` | `str` | Processor name  
`type` | `ProcessorType` | Type (EXTRACT, CLASSIFY, SPLITTER)  
`created_at` | `datetime \| None` | Creation timestamp  
`updated_at` | `datetime \| None` | Last update timestamp  
###  ProcessorVersion 
A specific version of a processor with full configuration.
Field | Type | Description  
---|---|---  
`id` | `str` | Version identifier  
`version` | `str` | Version name (draft, v1, etc.)  
`description` | `str` | Version description  
`processor` | `ProcessorMeta` | Processor metadata  
`config` | `ConfigClassification \| ConfigExtraction \| ConfigSplitter` | Processor configuration  
`created_at` | `datetime` | Creation timestamp  
`updated_at` | `datetime` | Last update timestamp  
###  ProcessorRun 
Represents a single execution of a processor.
Field | Type | Description  
---|---|---  
`id` | `str` | Run identifier  
`processor` | `ProcessorMeta` | Processor that was executed  
`output` | `ResultClassification \| ResultExtraction \| ResultSplitter \| None` | Processing results  
`status` | `RunStatus` | Current run status  
`files` | `list[StoredFile]` | Associated files  
`usage` | `int` | Total credits consumed  
###  StoredFile 
A file stored in Extend AI.
Field | Type | Description  
---|---|---  
`id` | `str` | Unique file identifier  
`type` | `str` | MIME type / file type  
`name` | `str` | File name  
###  Classification 
A classification category definition.
Field | Type | Description  
---|---|---  
`id` | `str` | Classification identifier  
`type` | `str` | Classification type/category name  
`description` | `str` | Description of this classification  
##  Result Structures 
###  ResultExtraction 
Output from an extraction processor.
Field | Type | Description  
---|---|---  
`value` | `dict` | Dictionary of extracted field values  
**Example:**
    ```python
    if run.status == RunStatus.PROCESSED:
        extracted = run.output.value
        print(f"Name: {extracted.get('name')}")
        print(f"Amount: {extracted.get('amount')}")
    ```
###  ResultClassification 
Output from a classification processor.
Field | Type | Description  
---|---|---  
`type` | `str` | Assigned classification type  
`confidence` | `float` | Confidence score (0.0 to 1.0)  
`insights` | `list[Insight]` | Extracted insights  
**Example:**
    ```python
    if run.status == RunStatus.PROCESSED:
        print(f"Type: {run.output.type}")
        print(f"Confidence: {run.output.confidence:.2%}")
        for insight in run.output.insights:
            print(f"  {insight.type}: {insight.content}")
    ```
###  ResultSplitter 
Output from a splitter processor.
Field | Type | Description  
---|---|---  
`splits` | `list[Split]` | List of identified splits  
**Example:**
    ```python
    if run.status == RunStatus.PROCESSED:
        for split in run.output.splits:
            print(f"Section: {split.type}")
            print(f"  Pages: {split.startPage} - {split.endPage}")
            print(f"  Observation: {split.observation}")
    ```
###  Split 
A document split/section identified by a splitter.
Field | Type | Description  
---|---|---  
`id` | `str` | Split identifier  
`type` | `str` | Split type/category  
`observation` | `str` | Observations about this split  
`identifier` | `str` | Unique identifier  
`startPage` | `int` | Starting page number  
`endPage` | `int` | Ending page number  
`classificationId` | `str` | Associated classification ID  
`fileId` | `str` | File this split belongs to  
`name` | `str` | Split name  
###  Insight 
An insight extracted during classification.
Field | Type | Description  
---|---|---  
`type` | `str` | Insight type/category  
`content` | `str` | Insight text content  
##  Configuration Structures 
###  ConfigExtraction 
Configuration for extraction processors.
Field | Type | Description  
---|---|---  
`base_processor` | `BaseProcessor` | Performance or light variant  
`extraction_rule` | `str` | Custom extraction instructions  
`schema` | `dict` | JSON Schema for extracted data  
`advanced_options` | `AdvancedOptionsExtraction` | Advanced settings  
`parser` | `Parser` | Document parser settings  
###  ConfigClassification 
Configuration for classification processors.
Field | Type | Description  
---|---|---  
`classifications` | `list[Classification]` | Possible classification categories  
`base_processor` | `BaseProcessor` | Performance or light variant  
`classification_rule` | `str` | Custom classification rules  
`advanced_options` | `AdvancedOptionsClassification` | Advanced settings  
`parser` | `Parser` | Document parser settings  
###  ConfigSplitter 
Configuration for splitter processors.
Field | Type | Description  
---|---|---  
`split_classifications` | `list[Classification]` | Classification categories for splits  
`base_processor` | `BaseProcessor` | Performance or light variant  
`split_rules` | `str` | Custom splitting rules  
`advanced_options` | `AdvancedOptionsSplitter` | Advanced settings  
`parser` | `Parser` | Document parser settings  
##  Constants (Enums) 
###  ProcessorType 
Types of processors available.
Value | Description  
---|---  
`EXTRACT` | Extracts structured data based on a schema  
`CLASSIFY` | Classifies documents into categories  
`SPLITTER` | Splits documents into sections  
###  RunStatus 
Status values for processor runs.
Value | Description  
---|---  
`PENDING` | Run is queued and waiting to start  
`PROCESSING` | Run is currently being processed  
`PROCESSED` | Run completed successfully  
`FAILED` | Run encountered an error  
`CANCELLED` | Run was cancelled before completion  
###  VersionName 
Standard version names for processors.
Value | Description  
---|---  
`LATEST` | Latest published version  
`DRAFT` | Draft/working version  
###  BaseProcessor 
Base processor variants (performance vs speed trade-off).
Value | Description  
---|---  
`CLASSIFICATION_PERFORMANCE` | High accuracy classification  
`CLASSIFICATION_LIGHT` | Fast classification  
`EXTRACTION_PERFORMANCE` | High accuracy extraction  
`EXTRACTION_LIGHT` | Fast extraction  
`SPLITTING_PERFORMANCE` | High accuracy splitting  
`SPLITTING_LIGHT` | Fast splitting  
##  Error Handling 
###  RequestFailed 
Exception raised when an Extend AI API request fails (extends `RuntimeError`).
Attribute | Type | Description  
---|---|---  
`status_code` | `int` | HTTP status code  
`message` | `str` | Error message from Extend AI  
**Example:**
    ```python
    try:
        run = client.run_processor(...)
    except RequestFailed as e:
        print(f"API Error {e.status_code}: {e.message}")
    ```
##  Polling Pattern 
Since document processing is asynchronous, use this pattern to wait for results:
    ```python
    import time
    from canvas_sdk.clients.extend_ai.constants import RunStatus
    def wait_for_completion(client, run_id: str, timeout_seconds: int = 120) -> ProcessorRun:
        """
        Wait for a processor run to complete.
        Args:
            client: Extend AI client instance
            run_id: The run ID to monitor
            timeout_seconds: Maximum time to wait
        Returns:
            ProcessorRun with final status
        Raises:
            TimeoutError: If processing exceeds timeout
        """
        start_time = time.time()
        poll_interval = 2  # seconds
        while True:
            run = client.run_status(run_id)
            # Check if done
            if run.status not in (RunStatus.PENDING, RunStatus.PROCESSING):
                return run
            # Check timeout
            if time.time() - start_time > timeout_seconds:
                raise TimeoutError(f"Processing timed out after {timeout_seconds}s")
            time.sleep(poll_interval)
    # Usage
    run = client.run_processor(processor_id, file_name, file_url, None)
    final_run = wait_for_completion(client, run.id)
    if final_run.status == RunStatus.PROCESSED:
        print(final_run.output.to_dict())
    ```
##  Additional Resources 
  - [Extend AI Documentation](https://docs.extend.ai/)
  - [Example Plugin](/sdk/example-extend_ai_pdf/) \- Documentation for the example plugin
  - [Source Code](https://github.com/canvas-medical/canvas-plugins/tree/main/example-plugins/extend_ai_pdf) \- View the source on GitHub
----- END PAGE https://docs.canvasmedical.com/sdk/clients-extend-ai/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/clients-llms/
The Canvas SDK LLMs client provides a unified interface for interacting with multiple Large Language Model (LLM) providers including OpenAI (GPT Models), Anthropic (Claude), and Google (Gemini). It supports text conversations, file attachments (images, PDFs, text), and structured JSON output.
##  Requirements 
Depending on which LLM provider you use:
  - **OpenAI** : API key from https://platform.openai.com/api-keys
  - **Anthropic** : API key from https://console.anthropic.com/settings/keys
  - **Google** : API key from https://aistudio.google.com/apikey
##  Imports 
The LLMs client is included in the Canvas SDK. Import the necessary components:
    ```python
    from canvas_sdk.clients.llms import (
        LlmOpenai,
        LlmAnthropic,
        LlmGoogle,
        LlmResponse,
        LlmTokens,
        LlmTurn,
    )
    from canvas_sdk.clients.llms.structures.settings import (
        LlmSettingsGpt4,
        LlmSettingsAnthropic,
        LlmSettingsGemini,
    )
    from canvas_sdk.clients.llms.constants import FileType
    from canvas_sdk.clients.llms.structures import LlmFileUrl, FileContent, BaseModelLlmJson
    ```
##  Initialize the Clients 
###  OpenAI (GPT Models) 
    ```python
    from canvas_sdk.clients.llms import LlmOpenai
    from canvas_sdk.clients.llms.structures.settings import LlmSettingsGpt4
    client = LlmOpenai(LlmSettingsGpt4(
        api_key="your_openai_api_key",
        model="gpt-4o",
        temperature=0.7,
    ))
    ```
###  Anthropic (Claude) 
    ```python
    from canvas_sdk.clients.llms import LlmAnthropic
    from canvas_sdk.clients.llms.structures.settings import LlmSettingsAnthropic
    client = LlmAnthropic(LlmSettingsAnthropic(
        api_key="your_anthropic_api_key",
        model="claude-sonnet-4-5-20250929",
        temperature=0.7,
        max_tokens=8192,
    ))
    ```
###  Google (Gemini) 
    ```python
    from canvas_sdk.clients.llms import LlmGoogle
    from canvas_sdk.clients.llms.structures.settings import LlmSettingsGemini
    client = LlmGoogle(LlmSettingsGemini(
        api_key="your_google_api_key",
        model="models/gemini-2.0-flash",
        temperature=0.7,
    ))
    ```
##  Simple Text Conversation 
    ```python
    from http import HTTPStatus
    from canvas_sdk.clients.llms import LlmOpenai
    from canvas_sdk.clients.llms.structures.settings import LlmSettingsGpt4
    # Initialize client
    client = LlmOpenai(LlmSettingsGpt4(
        api_key="your_api_key",
        model="gpt-4o",
        temperature=0.7,
    ))
    # Set up the conversation
    client.set_system_prompt(["You are a helpful assistant."])
    client.set_user_prompt(["What is the capital of France?"])
    # Make the request
    response = client.request()
    if response.code == HTTPStatus.OK:
        print(f"Response: {response.response}")
        print(f"Tokens used - Prompt: {response.tokens.prompt}, Generated: {response.tokens.generated}")
    else:
        print(f"Error: {response.response}")
    ```
##  Multi-turn Conversation 
    ```python
    # Initialize client
    client = LlmOpenai(LlmSettingsGpt4(
        api_key="your_api_key",
        model="gpt-4o",
        temperature=0.7,
    ))
    # Build a multi-turn conversation
    client.set_system_prompt(["You are a helpful math tutor."])
    client.set_user_prompt(["What is 2 + 2?"])
    client.set_model_prompt(["2 + 2 equals 4."])
    client.set_user_prompt(["And what is that multiplied by 3?"])
    # Get the response
    response = client.request()
    print(response.response)  # "4 multiplied by 3 equals 12."
    ```
##  Using Retry Logic 
    ```python
    # Attempt multiple requests until success or max attempts
    responses = client.attempt_requests(attempts=3)
    # Check the last response
    last_response = responses[-1]
    if last_response.code == HTTPStatus.OK:
        print(f"Success: {last_response.response}")
    else:
        print(f"Failed after {len(responses)} attempts")
    ```
##  Analyze an Image 
    ```python
    from canvas_sdk.clients.llms import LlmOpenai
    from canvas_sdk.clients.llms.structures.settings import LlmSettingsGpt4
    from canvas_sdk.clients.llms.constants import FileType
    from canvas_sdk.clients.llms.structures import LlmFileUrl
    client = LlmOpenai(LlmSettingsGpt4(
        api_key="your_api_key",
        model="gpt-4o",
        temperature=0.5,
    ))
    # Set up prompts
    client.set_system_prompt(["You are an image analysis assistant."])
    client.set_user_prompt(["Describe what you see in this image."])
    # Add an image file
    client.add_url_file(LlmFileUrl(
        url="https://example.com/image.jpg",
        type=FileType.IMAGE
    ))
    # Get the analysis
    response = client.request()
    print(response.response)
    ```
##  Analyze a PDF Document 
    ```python
    from canvas_sdk.clients.llms import LlmAnthropic
    from canvas_sdk.clients.llms.structures.settings import LlmSettingsAnthropic
    from canvas_sdk.clients.llms.constants import FileType
    from canvas_sdk.clients.llms.structures import LlmFileUrl
    client = LlmAnthropic(LlmSettingsAnthropic(
        api_key="your_api_key",
        model="claude-sonnet-4-5-20250929",
        temperature=0.5,
        max_tokens=4096,
    ))
    # Set up prompts
    client.set_system_prompt(["You are a document analysis assistant."])
    client.set_user_prompt(["Summarize the key points in this document."])
    # Add a PDF file
    client.add_url_file(LlmFileUrl(
        url="https://example.com/document.pdf",
        type=FileType.PDF
    ))
    # Get the summary
    response = client.request()
    print(response.response)
    ```
##  Upload File Content Directly 
Instead of providing a URL, you can upload file content directly using `FileContent`. This is useful when you have the file data in memory (e.g., from a form upload).
    ```python
    import base64
    from canvas_sdk.clients.llms import LlmOpenai
    from canvas_sdk.clients.llms.structures.settings import LlmSettingsGpt4
    from canvas_sdk.clients.llms.structures import FileContent
    client = LlmOpenai(LlmSettingsGpt4(
        api_key="your_api_key",
        model="gpt-4o",
        temperature=0.5,
    ))
    # Read file content from disk or form upload
    with open("document.pdf", "rb") as f:
        file_bytes = f.read()
    # Create FileContent with base64-encoded data
    file_content = FileContent(
        mime_type="application/pdf",
        content=base64.b64encode(file_bytes),
        size=len(file_bytes),
    )
    # Add to the client's file_content list
    client.file_content.append(file_content)
    # Set up prompts
    client.set_system_prompt(["Analyze the provided document."])
    client.set_user_prompt(["What are the main topics covered in this document?"])
    # Get the analysis
    response = client.request()
    print(response.response)
    ```
**Supported MIME types for direct file content:**
MIME Type Pattern | Description  
---|---  
`image/*` | Images (PNG, JPEG, GIF, etc.)  
`application/pdf` | PDF documents  
`text/*` | Text files (Anthropic only)  
##  Structured JSON Output 
    ```python
    from pydantic import Field
    from canvas_sdk.clients.llms import LlmOpenai
    from canvas_sdk.clients.llms.structures.settings import LlmSettingsGpt4
    from canvas_sdk.clients.llms.structures import BaseModelLlmJson
    # Define your response schema
    class PersonInfo(BaseModelLlmJson):
        name: str = Field(description="The person's full name")
        age: int = Field(description="The person's age in years")
        occupation: str = Field(description="The person's job or profession")
    # Initialize client
    client = LlmOpenai(LlmSettingsGpt4(
        api_key="your_api_key",
        model="gpt-4o",
        temperature=0.3,
    ))
    # Set the schema for structured output
    client.set_schema(PersonInfo)
    # Set up prompts
    client.set_system_prompt(["Extract person information from the text."])
    client.set_user_prompt(["John Smith is a 35-year-old software engineer."])
    # Get structured response
    response = client.request()
    # Response will be valid JSON matching the PersonInfo schema
    print(response.response)  # {"name": "John Smith", "age": 35, "occupation": "software engineer"}
    ```
##  Nested Structured Output 
    ```python
    from pydantic import Field
    from canvas_sdk.clients.llms.structures import BaseModelLlmJson
    # Define nested schemas (all must extend BaseModelLlmJson)
    class Address(BaseModelLlmJson):
        street: str = Field(description="Street address")
        city: str = Field(description="City name")
        country: str = Field(description="Country name")
    class Person(BaseModelLlmJson):
        name: str = Field(description="Full name")
        address: Address = Field(description="Home address")
    # Use with client
    client.set_schema(Person)
    client.set_system_prompt(["Extract person and address information."])
    client.set_user_prompt(["Jane Doe lives at 123 Main St, New York, USA."])
    response = client.request()
    ```
##  LLM Clients 
All LLM clients inherit from `LlmApi` and share the same interface.
###  Available Clients 
Client | Provider | Settings Class | API Endpoint  
---|---|---|---  
`LlmOpenai` | OpenAI | `LlmSettingsGpt4` | `https://us.api.openai.com`  
`LlmAnthropic` | Anthropic | `LlmSettingsAnthropic` | `https://api.anthropic.com`  
`LlmGoogle` | Google | `LlmSettingsGemini` | `https://generativelanguage.googleapis.com`  
###  Constructor 
    ```python
    LlmOpenai(settings: LlmSettingsGpt4)
    LlmAnthropic(settings: LlmSettingsAnthropic)
    LlmGoogle(settings: LlmSettingsGemini)
    ```
###  Attributes 
Attribute | Type | Description  
---|---|---  
`settings` | `LlmSettings` | Configuration settings for the LLM API  
`prompts` | `list[LlmTurn]` | List of conversation turns  
`file_urls` | `list[LlmFileUrl]` | Files to attach via URL (use `add_url_file()`)  
`file_content` | `list[FileContent]` | Files to attach via direct content  
`schema` | `type[BaseModelLlmJson]` | Schema for structured JSON output  
###  Methods 
####  `set_system_prompt(text: list[str]) -> None`
Set or replace the system prompt. The system prompt is always placed at the beginning of the conversation.
**Parameters:**
Parameter | Type | Description  
---|---|---  
`text` | `list[str]` | List of text strings for the prompt  
####  `set_user_prompt(text: list[str]) -> None`
Add a user message to the conversation.
**Parameters:**
Parameter | Type | Description  
---|---|---  
`text` | `list[str]` | List of text strings for the prompt  
####  `set_model_prompt(text: list[str]) -> None`
Add a model/assistant response to the conversation history.
**Parameters:**
Parameter | Type | Description  
---|---|---  
`text` | `list[str]` | List of text strings for the response  
####  `add_prompt(prompt: LlmTurn) -> None`
Add a conversation turn using an `LlmTurn` object.
**Parameters:**
Parameter | Type | Description  
---|---|---  
`prompt` | `LlmTurn` | The conversation turn to add  
####  `add_url_file(url_file: LlmFileUrl) -> None`
Add a file attachment to the next user message.
**Parameters:**
Parameter | Type | Description  
---|---|---  
`url_file` | `LlmFileUrl` | File URL and type information  
####  `set_schema(schema: type[BaseModelLlmJson] | None) -> None`
Set a schema for structured JSON output. Pass `None` to disable structured output.
**Parameters:**
Parameter | Type | Description  
---|---|---  
`schema` | `type[BaseModelLlmJson] | None` | Pydantic model for JSON schema  
####  `reset_prompts() -> None`
Clear all stored prompts from the conversation.
####  `request() -> LlmResponse`
Make a single request to the LLM API.
**Returns:** `LlmResponse` containing status code, response text, and token usage.
####  `attempt_requests(attempts: int) -> list[LlmResponse]`
Attempt multiple requests until success or max attempts reached.
**Parameters:**
Parameter | Type | Description  
---|---|---  
`attempts` | `int` | Maximum number of request attempts  
**Returns:** List of all `LlmResponse` objects from each attempt.
##  Settings Classes 
###  LlmSettings (Base) 
Base configuration class for LLM APIs.
Field | Type | Description  
---|---|---  
`api_key` | `str` | API authentication key  
`model` | `str` | Model name or identifier  
###  LlmSettingsGpt4 
Settings for OpenAI API.
Field | Type | Description  
---|---|---  
`api_key` | `str` | OpenAI API key  
`model` | `str` | Model name (e.g., `gpt-4o`, `gpt-4-turbo`)  
`temperature` | `float` | Randomness control (0.0-2.0)  
**Example:**
    ```python
    LlmSettingsGpt4(
        api_key="sk-...",
        model="gpt-4o",
        temperature=0.7,
    )
    ```
###  LlmSettingsAnthropic 
Settings for Anthropic Claude API.
Field | Type | Description  
---|---|---  
`api_key` | `str` | Anthropic API key  
`model` | `str` | Model name (e.g., `claude-sonnet-4-5-20250929`)  
`temperature` | `float` | Randomness control (0.0-1.0)  
`max_tokens` | `float` | Maximum tokens to generate  
**Example:**
    ```python
    LlmSettingsAnthropic(
        api_key="sk-ant-...",
        model="claude-sonnet-4-5-20250929",
        temperature=0.7,
        max_tokens=8192,
    )
    ```
###  LlmSettingsGemini 
Settings for Google Gemini API.
Field | Type | Description  
---|---|---  
`api_key` | `str` | Google API key  
`model` | `str` | Model name (e.g., `models/gemini-2.0-flash`)  
`temperature` | `float` | Randomness control (0.0-2.0)  
**Example:**
    ```python
    LlmSettingsGemini(
        api_key="AIza...",
        model="models/gemini-2.0-flash",
        temperature=0.7,
    )
    ```
##  Data Structures 
###  LlmResponse 
Response from an LLM API call.
Field | Type | Description  
---|---|---  
`code` | `HTTPStatus` | HTTP status code of the response  
`response` | `str` | Text content returned by the LLM  
`tokens` | `LlmTokens` | Token usage information  
**Methods:**
Method | Returns | Description  
---|---|---  
`to_dict()` | `dict` | Convert response to dictionary  
###  LlmTokens 
Token usage information for LLM API calls.
Field | Type | Description  
---|---|---  
`prompt` | `int` | Number of tokens in the prompt  
`generated` | `int` | Number of tokens in the generated response  
**Methods:**
Method | Returns | Description  
---|---|---  
`add(counts)` | `None` | Add token counts from another instance  
`to_dict()` | `dict` | Convert to dictionary  
###  LlmTurn 
A single conversation turn in an LLM interaction.
Field | Type | Description  
---|---|---  
`role` | `str` | Role of the speaker (`system`, `user`, `model`)  
`text` | `list[str]` | List of text strings for this turn  
**Methods:**
Method | Returns | Description  
---|---|---  
`to_dict()` | `dict` | Convert turn to dictionary  
`load_from_dict(dict_list)` | `list[LlmTurn]` | Create turns from list of dicts  
###  LlmFileUrl 
Container for file URL and type information.
Field | Type | Description  
---|---|---  
`url` | `str` | URL where the file can be accessed  
`type` | `FileType` | Type of file (IMAGE, PDF, TEXT)  
###  FileContent 
Container for file content, used for direct file uploads to LLM providers. Add instances to `client.file_content` list.
Field | Type | Description  
---|---|---  
`mime_type` | `str` | MIME type of the content (e.g., `image/png`)  
`content` | `bytes` | Base64-encoded file content  
`size` | `int` | Size of the original file in bytes  
**Example:**
    ```python
    import base64
    from canvas_sdk.clients.llms.structures import FileContent
    # From file bytes
    with open("image.png", "rb") as f:
        file_bytes = f.read()
    file_content = FileContent(
        mime_type="image/png",
        content=base64.b64encode(file_bytes),
        size=len(file_bytes),
    )
    # Add to client
    client.file_contents.append(file_content)
    ```
###  BaseModelLlmJson 
Base class for structured JSON output schemas. Extends Pydantic's `BaseModel` with:
  - `additionalProperties: false` in JSON schema
  - Automatic camelCase field name conversion
**Usage:**
    ```python
    from pydantic import Field
    from canvas_sdk.clients.llms.structures import BaseModelLlmJson
    class MySchema(BaseModelLlmJson):
        field_name: str = Field(description="Description for the LLM")
        another_field: int = Field(description="Another description")
    ```
##  Constants (Enums) 
###  FileType 
Supported file types for LLM file attachments.
Value | Description  
---|---  
`IMAGE` | Image files (PNG, JPEG, GIF)  
`PDF` | PDF documents  
`TEXT` | Plain text files  
###  Role Constants 
Available on all LLM client classes:
Constant | Value | Description  
---|---|---  
`ROLE_SYSTEM` | `"system"` | System/instruction role  
`ROLE_USER` | `"user"` | User message role  
`ROLE_MODEL` | `"model"` | Model/assistant response role  
##  Complete Plugin Example 
Here's a complete example of using the LLMs client in a Canvas plugin:
    ```python
    import base64
    from http import HTTPStatus
    from pydantic import Field
    from canvas_sdk.clients.llms import LlmOpenai
    from canvas_sdk.clients.llms.constants import FileType
    from canvas_sdk.clients.llms.structures import BaseModelLlmJson, FileContent, LlmFileUrl
    from canvas_sdk.clients.llms.structures.settings import LlmSettingsGpt4
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.simple_api import JSONResponse, PlainTextResponse, Response
    from canvas_sdk.handlers.simple_api import Credentials, SimpleAPI, api
    from canvas_sdk.handlers.simple_api.api import FileFormPart, StringFormPart
    class AnimalCount(BaseModelLlmJson):
        """Structured response for animal counting."""
        dogs: int = Field(description="Number of dogs in the image")
        cats: int = Field(description="Number of cats in the image")
        total: int = Field(description="Total number of animals")
    class LlmHandler(SimpleAPI):
        """Simple API handler for LLM operations."""
        def authenticate(self, credentials: Credentials) -> bool:
            return True
        def _llm_client(self) -> LlmOpenai:
            """Create LLM client from plugin secrets."""
            return LlmOpenai(LlmSettingsGpt4(
                api_key=self.secrets["LlmKey"],
                model="gpt-4o",
                temperature=0.5,
            ))
        @api.post("/chat")
        def chat(self) -> list[Response | Effect]:
            """Handle a chat conversation."""
            client = self._llm_client()
            # Process conversation turns from request
            for turn in self.request.json():
                if turn.get("role") == "system":
                    client.set_system_prompt([turn.get("prompt", "")])
                elif turn.get("role") == "user":
                    client.set_user_prompt([turn.get("prompt", "")])
                else:
                    client.set_model_prompt([turn.get("prompt", "")])
            response = client.attempt_requests(attempts=2)[0]
            return [PlainTextResponse(response.response, status_code=response.code)]
        @api.post("/analyze_image")
        def analyze_image(self) -> list[Response | Effect]:
            """Analyze an image for animal content via URL."""
            client = self._llm_client()
            url = self.request.json().get("url")
            if not url:
                return [JSONResponse({"error": "URL required"}, status_code=HTTPStatus.BAD_REQUEST)]
            # Set up structured output
            client.set_schema(AnimalCount)
            client.set_system_prompt(["Count the animals in the provided image."])
            client.set_user_prompt(["Identify and count all animals in this image."])
            client.add_url_file(LlmFileUrl(url=url, type=FileType.IMAGE))
            responses = client.attempt_requests(attempts=2)
            content = [r.to_dict() for r in responses]
            return [JSONResponse(content, status_code=HTTPStatus.OK)]
        @api.post("/file")
        def file(self) -> list[Response | Effect]:
            """Analyze uploaded file content using LLM.
            Accepts multipart form data with 'file' and 'input' fields.
            """
            content = b""
            mime_type = ""
            user_input = ""
            # Parse form data
            form_data = self.request.form_data()
            if "file" in form_data and isinstance(form_data["file"], FileFormPart):
                content = form_data["file"].content
                mime_type = form_data["file"].content_type
            if "input" in form_data and isinstance(form_data["input"], StringFormPart):
                user_input = form_data["input"].value
            if not (content and mime_type and user_input):
                return [PlainTextResponse("Missing file or input", status_code=HTTPStatus.BAD_REQUEST)]
            client = self._llm_client()
            # Create FileContent with base64-encoded data
            file = FileContent(
                mime_type=mime_type,
                content=base64.b64encode(content),
                size=len(content),
            )
            client.file_content.append(file)
            client.set_system_prompt(["Answer the question about the file clearly and concisely."])
            client.set_user_prompt([user_input])
            response = client.attempt_requests(attempts=1)[0]
            return [PlainTextResponse(response.response, status_code=response.code)]
    ```
##  Error Handling 
The LLM clients return `LlmResponse` objects with HTTP status codes indicating success or failure.
    ```python
    from http import HTTPStatus
    response = client.request()
    if response.code == HTTPStatus.OK:
        print(f"Success: {response.response}")
    elif response.code == HTTPStatus.TOO_MANY_REQUESTS:
        print("Rate limited - try again later")
    elif response.code == HTTPStatus.UNAUTHORIZED:
        print("Invalid API key")
    elif response.code == HTTPStatus.BAD_REQUEST:
        print(f"Bad request: {response.response}")
    else:
        print(f"Error {response.code}: {response.response}")
    ```
When using `attempt_requests`, the method will automatically retry on failure:
    ```python
    responses = client.attempt_requests(attempts=3)
    # Check if any attempt succeeded
    successful = [r for r in responses if r.code == HTTPStatus.OK]
    if successful:
        print(f"Success after {len(responses)} attempt(s)")
    else:
        print(f"All {len(responses)} attempts failed")
    ```
##  Provider-Specific Notes 
###  OpenAI 
  - Uses the Responses API (`/v1/responses`)
  - Supports images and PDFs via URL (`add_url_file`) or direct content (`file_content`)
  - System prompts are sent as `instructions`
  - Direct file content uses `input_image` for images and `input_file` for PDFs
###  Anthropic 
  - Uses the Messages API (`/v1/messages`)
  - Supports images, PDFs, and text files via URL or direct content
  - Text files are base64-decoded and sent as plain text
  - Structured output uses tool calling
###  Google Gemini 
  - Uses the Generative Language API
  - Files via URL are downloaded and converted to base64 automatically
  - Supports both URL-based and direct file content
  - Maximum file size limit of 10MB per request (combined)
  - Structured output uses `responseJsonSchema`
##  Additional Resources 
  - [OpenAI API Documentation](https://platform.openai.com/docs/api-reference)
  - [Anthropic API Documentation](https://docs.anthropic.com/en/api)
  - [Google Gemini API Documentation](https://ai.google.dev/gemini-api/docs)
  - [Example Plugin](/sdk/example-llm/) \- Documentation for the example plugin
  - [Source Code](https://github.com/canvas-medical/canvas-plugins/tree/main/example-plugins/llm) \- View the source on GitHub
----- END PAGE https://docs.canvasmedical.com/sdk/clients-llms/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/clients-sendgrid/
The Canvas SDK SendGrid client provides a simple interface for sending emails, managing webhooks, and querying email logs using the SendGrid API.
##  Requirements 
  - **SendGrid API Key** : Create one at https://app.sendgrid.com/settings/api_keys
  - **Authenticated Domain** : Configure at https://app.sendgrid.com/settings/sender_auth
##  Imports 
The SendGrid client is included in the Canvas SDK. Import the necessary components:
    ```python
    from canvas_sdk.clients.sendgrid.libraries import EmailClient
    from canvas_sdk.clients.sendgrid.constants import RecipientType
    from canvas_sdk.clients.sendgrid.structures import (
        Address,
        BodyContent,
        Email,
        Recipient,
        RequestFailed,
        Settings,
    )
    ```
##  Initialize the Client 
    ```python
    client = EmailClient(Settings(key="your_sendgrid_api_key"))
    ```
##  Send a Simple Text Email 
    ```python
    from canvas_sdk.clients.sendgrid.libraries import EmailClient
    from canvas_sdk.clients.sendgrid.constants import RecipientType
    from canvas_sdk.clients.sendgrid.structures import (
        Address, BodyContent, Email, Recipient, RequestFailed, Settings
    )
    client = EmailClient(Settings(key="your_api_key"))
    email = Email(
        sender=Address(email="sender@example.com", name="Sender Name"),
        reply_tos=[Address(email="reply@example.com", name="Reply To")],
        recipients=[
            Recipient(address=Address(email="recipient@example.com", name="Recipient"), type=RecipientType.TO)
        ],
        subject="Hello from Canvas SDK",
        bodies=[BodyContent(type="text/plain", value="This is a test email.")],
        attachments=[],
        send_at=Email.now(),
    )
    try:
        client.simple_send(email)
        print("Email sent successfully!")
    except RequestFailed as e:
        print(f"Failed to send email: {e.message} (HTTP {e.status_code})")
    ```
##  Send an HTML Email with CC 
    ```python
    email = Email(
        sender=Address(email="sender@example.com", name="Sender"),
        reply_tos=[Address(email="reply@example.com", name="Reply To")],
        recipients=[
            Recipient(address=Address(email="to@example.com", name="To"), type=RecipientType.TO),
            Recipient(address=Address(email="cc@example.com", name="CC"), type=RecipientType.CC),
        ],
        subject="HTML Email Example",
        bodies=[
            BodyContent(type="text/plain", value="Plain text fallback"),
            BodyContent(type="text/html", value="<html><body><h1>Hello!</h1><p>This is HTML content.</p></body></html>"),
        ],
        attachments=[],
        send_at=Email.now(),
    )
    client.simple_send(email)
    ```
##  Send an Email with Attachment 
    ```python
    from canvas_sdk.clients.sendgrid.structures import Attachment
    # Create attachment from URL
    attachment = Attachment.from_url(
        url="https://example.com/document.pdf",
        headers={},
        filename="document.pdf"
    )
    email = Email(
        sender=Address(email="sender@example.com", name="Sender"),
        reply_tos=[Address(email="reply@example.com", name="Reply To")],
        recipients=[
            Recipient(address=Address(email="to@example.com", name="To"), type=RecipientType.TO)
        ],
        subject="Email with Attachment",
        bodies=[BodyContent(type="text/plain", value="Please find attached document.")],
        attachments=[attachment],
        send_at=Email.now(),
    )
    client.simple_send(email)
    ```
##  Send an Email with Inline Image 
    ```python
    # Create inline image attachment
    inline_image = Attachment.from_url_inline(
        url="https://example.com/logo.png",
        headers={},
        filename="logo.png",
        content_id="logo123"
    )
    email = Email(
        sender=Address(email="sender@example.com", name="Sender"),
        reply_tos=[Address(email="reply@example.com", name="Reply To")],
        recipients=[
            Recipient(address=Address(email="to@example.com", name="To"), type=RecipientType.TO)
        ],
        subject="Email with Inline Image",
        bodies=[
            BodyContent(type="text/plain", value="See image in HTML version"),
            BodyContent(type="text/html", value='<html><body><img src="cid:logo123" width="200"/></body></html>'),
        ],
        attachments=[inline_image],
        send_at=Email.now(),
    )
    client.simple_send(email)
    ```
##  Query Sent Emails 
    ```python
    from datetime import datetime
    from canvas_sdk.clients.sendgrid.constants import CriterionOperation
    from canvas_sdk.clients.sendgrid.structures import CriterionDatetime, LoggedEmailCriteria
    criteria = LoggedEmailCriteria(
        message_id="",
        subject="",
        to_email="recipient@example.com",
        reason="",
        status=[],
        message_created_at=[
            CriterionDatetime(
                date_time=datetime(2024, 1, 1),
                operation=CriterionOperation.GREATER_THAN_OR_EQUAL
            )
        ],
    )
    for email in client.logged_emails(criteria, up_to=10):
        print(f"Subject: {email.subject}, Status: {email.status.value}")
    ```
##  EmailClient 
The main class for interacting with the SendGrid API.
###  Constructor 
    ```python
    EmailClient(settings: Settings)
    ```
Parameter | Type | Description  
---|---|---  
`settings` | `Settings` | Configuration object containing the API key  
###  Sending Emails 
####  `simple_send(email: Email) -> bool`
Send an email using a structured `Email` object. This is the recommended method for most use cases.
**Returns:** `True` on success
**Raises:** `RequestFailed` on error
####  `prepared_send(data: dict) -> bool`
Send an email using a raw dictionary following SendGrid's API schema. Use this for advanced cases not covered by `simple_send`.
**Parameters:**
  - `data`: Dictionary following [SendGrid's mail send schema](https://www.twilio.com/docs/sendgrid/api-reference/mail-send/mail-send#request-body)
**Returns:** `True` on success
**Raises:** `RequestFailed` on error
###  Email Logs 
####  `logged_emails(criteria: LoggedEmailCriteria, up_to: int) -> Iterator[SentEmail]`
Query sent emails matching the specified criteria.
**Parameters:**
  - `criteria`: Filter criteria for the query
  - `up_to`: Maximum number of results to return
**Returns:** Iterator of `SentEmail` objects
####  `logged_email(message_id: str) -> SentEmailDetail`
Get detailed information about a specific email including its event history.
**Parameters:**
  - `message_id`: The SendGrid message ID
**Returns:** `SentEmailDetail` object with full event history
###  Inbound Parse Webhooks 
Configure webhooks to receive incoming emails.
Method | Description  
---|---  
`parser_setting_add(setting: ParseSetting) -> ParseSetting` | Create a new inbound parse webhook  
`parser_setting_delete(hostname: str) -> bool` | Delete a webhook by hostname  
`parser_setting_get(hostname: str) -> ParseSetting` | Get webhook configuration by hostname  
`parser_setting_list() -> Iterator[ParseSetting]` | List all inbound parse webhooks  
Requires MX record setup pointing to `mx.sendgrid.net`, for example:
host | type | priority | TTL | value  
---|---|---|---|---  
`canvas` | `MX` | 10 | 1 hr | `mx.sendgrid.net`  
###  Event Webhooks 
Configure webhooks to receive email delivery status notifications.
Method | Description  
---|---  
`event_webhook_add(event: EventWebhook) -> EventWebhookRecord` | Create a new event webhook  
`event_webhook_delete(event_webhook_id: str) -> bool` | Delete a webhook by ID  
`event_webhook_get(event_webhook_id: str) -> EventWebhookRecord` | Get webhook by ID  
`event_webhook_list() -> Iterator[EventWebhookRecord]` | List all event webhooks  
`event_webhook_sign(event_webhook_id: str, enabled: bool) -> str` | Enable/disable signature verification, returns public key  
##  Data Structures 
###  Settings 
Configuration for the EmailClient.
Field | Type | Description  
---|---|---  
`key` | `str` | SendGrid API key  
###  Address 
Represents an email address with display name.
Field | Type | Description  
---|---|---  
`email` | `str` | Email address  
`name` | `str` | Display name  
###  Recipient 
Represents an email recipient with type.
Field | Type | Description  
---|---|---  
`address` | `Address` | Email address object  
`type` | `RecipientType` | TO, CC, or BCC  
###  BodyContent 
Represents email body content with MIME type.
Field | Type | Description  
---|---|---  
`type` | `str` | MIME type (e.g., `text/plain`, `text/html`)  
`value` | `str` | Content  
###  Email 
Complete email message structure.
Field | Type | Description  
---|---|---  
`sender` | `Address` | Sender email address  
`reply_tos` | `list[Address]` | Reply-to addresses  
`recipients` | `list[Recipient]` | List of recipients (TO/CC/BCC)  
`subject` | `str` | Email subject line  
`bodies` | `list[BodyContent]` | Email body content(s)  
`attachments` | `list[Attachment]` | File attachments  
`send_at` | `int` | Unix timestamp for sending  
**Class Methods:**
Method | Description  
---|---  
`Email.now() -> int` | Get current timestamp for immediate send  
`Email.timestamp(dt: datetime) -> int` | Convert datetime to Unix timestamp  
###  Attachment 
Represents an email attachment.
Field | Type | Description  
---|---|---  
`content_id` | `str` | ID for inline references  
`content` | `str` | Base64 encoded content  
`type` | `str` | MIME type  
`filename` | `str` | Filename  
`disposition` | `AttachmentDisposition` | ATTACHMENT or INLINE  
**Class Methods:**
Method | Description  
---|---  
`Attachment.from_url(url, headers, filename) -> Attachment` | Create attachment from URL  
`Attachment.from_url_inline(url, headers, filename, content_id) -> Attachment` | Create inline attachment from URL  
###  LoggedEmailCriteria 
Search criteria for querying sent emails.
Field | Type | Description  
---|---|---  
`message_id` | `str` | Filter by message ID  
`subject` | `str` | Filter by subject  
`to_email` | `str` | Filter by recipient email  
`reason` | `str` | Filter by reason  
`status` | `list[StatusEmail]` | Filter by status(es)  
`message_created_at` | `list[CriterionDatetime]` | Filter by creation date/time  
###  CriterionDatetime 
DateTime comparison for email queries.
Field | Type | Description  
---|---|---  
`date_time` | `datetime` | Date/time to compare  
`operation` | `CriterionOperation` | Comparison operator  
###  SentEmail 
Basic information about a sent email (returned by `logged_emails`).
Field | Type | Description  
---|---|---  
`from_email` | `str` | Sender email address  
`message_id` | `str` | SendGrid message ID  
`subject` | `str` | Email subject  
`to_email` | `str` | Recipient email address  
`reason` | `str` | Delivery failure reason  
`status` | `StatusEmail` | Delivery status  
`created_at` | `datetime` | Creation timestamp  
###  SentEmailDetail 
Detailed sent email information with event history (returned by `logged_email`).
Field | Type | Description  
---|---|---  
`from_email` | `str` | Sender email address  
`message_id` | `str` | SendGrid message ID  
`subject` | `str` | Email subject  
`to_email` | `str` | Recipient email address  
`status` | `StatusEmail` | Current delivery status  
`events` | `list[EmailEvent]` | List of lifecycle events  
###  EmailEvent 
Represents an event in an email's lifecycle.
Field | Type | Description  
---|---|---  
`event` | `EventEmail` | Event type  
`email` | `str` | Recipient email address  
`message_id` | `str` | SendGrid message ID  
`event_id` | `str` | Unique event ID  
`on_datetime` | `datetime` | Event timestamp  
`reason` | `str` | Reason (for bounce, dropped events)  
`response` | `str` | Server response (for delivered events)  
`url` | `str` | Clicked/opened URL (for click, open)  
`attempt` | `int` | Delivery attempt number (for deferred)  
###  ParseSetting 
Configuration for inbound email parsing.
Field | Type | Description  
---|---|---  
`url` | `str` | Webhook URL to receive parsed emails  
`hostname` | `str` | Domain to receive emails (requires MX record)  
`spam_check` | `bool` | Enable spam filtering  
`send_raw` | `bool` | Send raw MIME message instead of parsed  
###  EventWebhook 
Configuration for outbound email event notifications.
Field | Type | Description  
---|---|---  
`enabled` | `bool` | Whether webhook is active  
`url` | `str` | Webhook URL  
`friendly_name` | `str` | Display name  
`delivered` | `bool` | Track delivered events  
`bounce` | `bool` | Track bounce events  
`dropped` | `bool` | Track dropped events  
`spam_report` | `bool` | Track spam report events  
`processed` | `bool` | Track processed events  
`open` | `bool` | Track open events  
`click` | `bool` | Track click events  
`unsubscribe` | `bool` | Track unsubscribe events  
`group_resubscribe` | `bool` | Track group resubscribe events  
`group_unsubscribe` | `bool` | Track group unsubscribe events  
###  EventWebhookRecord 
Stored event webhook with metadata (extends EventWebhook).
Field | Type | Description  
---|---|---  
_(all fields from EventWebhook)_ |  |   
`id` | `str` | Webhook ID  
`public_key` | `str` | Public key for signature verification  
`created_date` | `datetime` | Creation timestamp  
`updated_date` | `datetime` | Last update timestamp  
###  ParsedEmail 
Represents an inbound email received via the Inbound Parse webhook.
Field | Type | Description  
---|---|---  
`headers` | `list[ParsedHeader]` | Email headers  
`charsets` | `dict[str, str]` | Character set mappings  
`envelope` | `ParsedEnvelope` | SMTP envelope information  
`email_from` | `str` | Sender address  
`email_to` | `str` | Recipient address  
`subject` | `str` | Email subject  
`text` | `str` | Plain text body  
`html` | `str` | HTML body  
`attachments` | `int` | Number of attachments  
`attachment_info` | `dict[str, ParsedAttachment]` | Attachment metadata  
`content_ids` | `dict[str, str]` | Content ID mappings  
`spf` | `str` | SPF verification result  
`dkim` | `str` | DKIM verification result  
`spam_report` | `list[str]` | Spam analysis report  
`spam_score` | `float` | Spam score  
##  Constants (Enums) 
###  RecipientType 
Value | Description  
---|---  
`TO` | Primary recipient  
`CC` | Carbon copy  
`BCC` | Blind carbon copy  
###  AttachmentDisposition 
Value | Description  
---|---  
`ATTACHMENT` | Standard file attachment  
`INLINE` | Embedded in email body  
###  StatusEmail 
Email delivery status values.
Value | Description  
---|---  
`PROCESSED` | Email processed by SendGrid  
`DELIVERED` | Successfully delivered  
`NOT_DELIVERED` | Delivery failed  
`DEFERRED` | Temporarily delayed  
`DROPPED` | Dropped by SendGrid  
`BOUNCED` | Bounced back  
`BLOCKED` | Blocked by recipient  
###  EventEmail 
Email event types for webhooks.
Value | Description  
---|---  
`BOUNCE` | Email bounced  
`CLICK` | Link clicked  
`DEFERRED` | Delivery deferred  
`DELIVERED` | Email delivered  
`DROPPED` | Email dropped  
`CANCEL_DROP` | Drop cancelled  
`OPEN` | Email opened  
`PROCESSED` | Email processed  
`RECEIVED` | Inbound email received  
`SPAM_REPORT` | Reported as spam  
`GROUP_UNSUBSCRIBE` | Unsubscribed from group  
`GROUP_RESUBSCRIBE` | Resubscribed to group  
`UNSUBSCRIBE` | Unsubscribed  
###  CriterionOperation 
Comparison operators for email log queries.
Value | Symbol | Description  
---|---|---  
`GREATER_THAN` | `>` | Greater than  
`GREATER_THAN_OR_EQUAL` | `>=` | Greater than or equal  
`LOWER_THAN` | `<` | Less than  
`LOWER_THAN_OR_EQUAL` | `<=` | Less than or equal  
`EQUAL` | `=` | Equal to  
##  Error Handling 
###  RequestFailed 
Exception raised when a SendGrid API request fails (extends `RuntimeError`).
Attribute | Type | Description  
---|---|---  
`status_code` | `int` | HTTP status code  
`message` | `str` | Error message from SendGrid  
**Example:**
    ```python
    try:
        client.simple_send(email)
    except RequestFailed as e:
        print(f"Error {e.status_code}: {e.message}")
    ```
##  Webhook Setup Examples 
###  Inbound Parse Webhook (Receive Incoming Emails) 
    ```python
    from canvas_sdk.clients.sendgrid.structures import ParseSetting
    # Note: Requires MX record for the hostname pointing to mx.sendgrid.net
    setting = ParseSetting(
        url="https://your-app.com/api/incoming-email",
        hostname="mail.yourdomain.com",
        spam_check=True,
        send_raw=False,
    )
    try:
        created = client.parser_setting_add(setting)
        print(f"Inbound webhook created for {created.hostname}")
    except RequestFailed as e:
        print(f"Failed: {e.message}")
    ```
###  Event Webhook (Track Outbound Email Status) 
    ```python
    from canvas_sdk.clients.sendgrid.structures import EventWebhook
    webhook = EventWebhook(
        url="https://your-app.com/api/email-events",
        enabled=True,
        friendly_name="My Email Tracker",
        delivered=True,
        bounce=True,
        dropped=True,
        spam_report=True,
        processed=True,
        open=True,
        click=True,
        unsubscribe=False,
        group_resubscribe=False,
        group_unsubscribe=False,
    )
    try:
        created = client.event_webhook_add(webhook)
        print(f"Event webhook created with ID: {created.id}")
    except RequestFailed as e:
        print(f"Failed: {e.message}")
    ```
##  Additional Resources 
  - [SendGrid API Documentation](https://www.twilio.com/docs/sendgrid/api-reference)
  - [Inbound Parse Webhook Setup](https://www.twilio.com/docs/sendgrid/for-developers/parsing-email/inbound-email)
  - [Event Webhook Documentation](https://www.twilio.com/docs/sendgrid/for-developers/tracking-events/event)
  - [Example Plugin](/sdk/example-sendgrid_email/) \- Documentation for the example plugin
  - [Source Code](https://github.com/canvas-medical/canvas-plugins/tree/main/example-plugins/sendgrid_email) \- View the source on GitHub
----- END PAGE https://docs.canvasmedical.com/sdk/clients-sendgrid/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/clients-twilio/
The Canvas SDK Twilio client provides a simple interface for sending SMS and MMS messages, managing phone numbers, and handling webhooks using the Twilio API.
##  Requirements 
  - **Twilio Account SID** : Found in your [Twilio Console](https://console.twilio.com/)
  - **Twilio API Key and Secret** : Create at [API Keys](https://console.twilio.com/us1/account/keys-credentials/api-keys)
  - **Twilio Phone Number** : Purchase at [Phone Numbers](https://console.twilio.com/us1/develop/phone-numbers/manage/incoming)
##  Imports 
The Twilio client is included in the Canvas SDK. Import the necessary components:
    ```python
    from canvas_sdk.clients.twilio.libraries import SmsClient
    from canvas_sdk.clients.twilio.structures import Settings, SmsMms, RequestFailed
    ```
##  Initialize the Client 
    ```python
    settings = Settings(
        account_sid="your_account_sid",
        key="your_api_key",
        secret="your_api_secret",
    )
    client = SmsClient(settings)
    ```
##  Send a Simple SMS 
    ```python
    from canvas_sdk.clients.twilio.libraries import SmsClient
    from canvas_sdk.clients.twilio.structures import Settings, SmsMms, RequestFailed
    # Initialize the client
    settings = Settings(
        account_sid="ACxxxxxxxxxxxxxxxxx",
        key="SKxxxxxxxxxxxxxxxxx",
        secret="your_api_secret",
    )
    client = SmsClient(settings)
    # First, get your phone number SID
    phones = list(client.account_phone_numbers())
    phone = phones[0]  # Use the first phone number
    print(f"Using phone: {phone.phone_number} (SID: {phone.sid})")
    # Create and send the SMS
    sms = SmsMms(
        number_from=phone.phone_number,
        number_from_sid=phone.sid,
        number_to="+1234567890",
        message="Hello from Canvas SDK!",
        media_url="",
        status_callback_url="",
    )
    try:
        message = client.send_sms_mms(sms)
        print(f"Message sent! SID: {message.sid}, Status: {message.status.value}")
    except RequestFailed as e:
        print(f"Failed to send: {e.message} (HTTP {e.status_code})")
    ```
##  Send an MMS with Image 
    ```python
    # Create an MMS with an image attachment
    mms = SmsMms(
        number_from=phone.phone_number,
        number_from_sid=phone.sid,
        number_to="+1234567890",
        message="Check out this image!",
        media_url="https://example.com/image.jpg",
        status_callback_url="",
    )
    try:
        message = client.send_sms_mms(mms)
        print(f"MMS sent! SID: {message.sid}")
    except RequestFailed as e:
        print(f"Failed to send: {e.message}")
    ```
##  Send SMS with Status Callback 
    ```python
    # Send SMS with a callback URL to track delivery status
    sms = SmsMms(
        number_from=phone.phone_number,
        number_from_sid=phone.sid,
        number_to="+1234567890",
        message="Message with tracking",
        media_url="",
        status_callback_url="https://your-app.com/api/sms-status",
    )
    message = client.send_sms_mms(sms)
    print(f"Message queued with callback. SID: {message.sid}")
    ```
##  Retrieve Message History 
    ```python
    from canvas_sdk.clients.twilio.constants import DateOperation
    # Get all messages (no filters)
    for message in client.retrieve_all_sms("", "", "", DateOperation.ON_EXACTLY):
        print(f"{message.date_sent}: {message.number_from} -> {message.number_to}: {message.body}")
    # Get messages sent to a specific number
    for message in client.retrieve_all_sms("+1234567890", "", "", DateOperation.ON_EXACTLY):
        print(f"To {message.number_to}: {message.body}")
    # Get messages from a specific date onwards
    for message in client.retrieve_all_sms("", "", "2024-01-01", DateOperation.ON_AND_AFTER):
        print(f"{message.date_sent}: {message.body}")
    ```
##  Handle Inbound Messages (Webhook) 
When Twilio receives an SMS to your number, it can call your webhook. Parse the callback data:
    ```python
    from canvas_sdk.clients.twilio.structures import StatusInbound, TwiMlMessage
    def handle_inbound_sms(raw_body: str) -> str:
        """Process incoming SMS and return TwiML response."""
        # Parse the incoming message
        inbound = StatusInbound.callback_inbound(raw_body)
        print(f"Received from {inbound.number_from}: {inbound.body}")
        # Create a reply using TwiML
        if "hello" in inbound.body.lower():
            reply = TwiMlMessage.instance("Hello! Nice to hear from you!")
        else:
            reply = TwiMlMessage.instance("Thanks for your message!")
        return reply.to_xml()
    ```
##  Reply with MMS (TwiML) 
    ```python
    from canvas_sdk.clients.twilio.structures import TwiMlMessage
    # Create a TwiML response with text and image
    reply = TwiMlMessage.instance_with_media(
        message_text="Here's a picture for you!",
        media_url="https://example.com/image.jpg"
    )
    xml_response = reply.to_xml()
    # Returns TwiML like:
    # <?xml version="1.0" encoding="UTF-8"?>
    # <Response><Message><Body>Here's a picture for you!</Body><Media>https://example.com/image.jpg</Media></Message></Response>
    ```
##  SmsClient 
The main class for interacting with the Twilio SMS/MMS API.
###  Constructor 
    ```python
    SmsClient(settings: Settings)
    ```
Parameter | Type | Description  
---|---|---  
`settings` | `Settings` | Configuration object with Twilio credentials  
###  Phone Number Management 
####  `account_phone_numbers() -> Iterator[Phone]`
Retrieve all phone numbers associated with the Twilio account.
    ```python
    for phone in client.account_phone_numbers():
        print(f"{phone.friendly_name}: {phone.phone_number}")
        print(f"  SMS: {phone.capabilities.sms}, MMS: {phone.capabilities.mms}")
    ```
**Returns:** Iterator of `Phone` objects
**Raises:** `RequestFailed` on error
####  `account_phone_number(phone_sid: str) -> Phone`
Retrieve details for a specific phone number by its SID.
    ```python
    phone = client.account_phone_number("PNxxxxxxxxxxxxxxxxx")
    print(f"Phone: {phone.phone_number}, Status: {phone.status}")
    ```
Parameter | Type | Description  
---|---|---  
`phone_sid` | `str` | The Twilio SID of the phone  
**Returns:** `Phone` object
**Raises:** `RequestFailed` on error
####  `set_inbound_webhook(phone_sid: str, webhook_url: str, method: HttpMethod) -> bool`
Configure the webhook URL for receiving inbound messages on a phone number.
    ```python
    from canvas_sdk.clients.twilio.constants import HttpMethod
    success = client.set_inbound_webhook(
        phone_sid="PNxxxxxxxxxxxxxxxxx",
        webhook_url="https://your-app.com/api/inbound-sms",
        method=HttpMethod.POST
    )
    ```
Parameter | Type | Description  
---|---|---  
`phone_sid` | `str` | The Twilio SID of the phone  
`webhook_url` | `str` | URL to receive inbound messages  
`method` | `HttpMethod` | HTTP method (GET or POST)  
**Returns:** `True` on success
**Raises:** `RequestFailed` on error
###  Sending Messages 
####  `send_sms_mms(sms_mms: SmsMms) -> Message`
Send an SMS or MMS message. The method automatically validates phone capabilities.
    ```python
    sms = SmsMms(
        number_from="+15551234567",
        number_from_sid="PNxxxxxxxxxxxxxxxxx",
        number_to="+15559876543",
        message="Hello!",
        media_url="",  # Empty for SMS, URL for MMS
        status_callback_url="https://your-app.com/status",
    )
    message = client.send_sms_mms(sms)
    print(f"Sent! SID: {message.sid}, Status: {message.status.value}")
    ```
Parameter | Type | Description  
---|---|---  
`sms_mms` | `SmsMms` | Message details to send  
**Returns:** `Message` object with sent message details
**Raises:** `RequestFailed` if the phone lacks required capabilities or API fails
###  Retrieving Messages 
####  `retrieve_sms(message_id: str) -> Message`
Get details for a specific message by its SID.
    ```python
    message = client.retrieve_sms("SMxxxxxxxxxxxxxxxxx")
    print(f"Status: {message.status.value}")
    print(f"Body: {message.body}")
    print(f"Sent: {message.date_sent}")
    ```
Parameter | Type | Description  
---|---|---  
`message_id` | `str` | The Twilio message SID  
**Returns:** `Message` object
**Raises:** `RequestFailed` on error
####  `retrieve_all_sms(number_to, number_from, date_sent, date_operation) -> Iterator[Message]`
Retrieve messages with optional filtering.
    ```python
    from canvas_sdk.clients.twilio.constants import DateOperation
    # All messages
    for msg in client.retrieve_all_sms("", "", "", DateOperation.ON_EXACTLY):
        print(msg.body)
    # Messages to a specific number
    for msg in client.retrieve_all_sms("+15551234567", "", "", DateOperation.ON_EXACTLY):
        print(msg.body)
    # Messages from a specific date
    for msg in client.retrieve_all_sms("", "", "2024-06-01", DateOperation.ON_AND_AFTER):
        print(f"{msg.date_sent}: {msg.body}")
    ```
Parameter | Type | Description  
---|---|---  
`number_to` | `str` | Filter by recipient (empty = no filter)  
`number_from` | `str` | Filter by sender (empty = no filter)  
`date_sent` | `str` | Date to filter by (YYYY-MM-DD format)  
`date_operation` | `DateOperation` | How to compare the date  
**Returns:** Iterator of `Message` objects
**Raises:** `RequestFailed` on error
####  `delete_sms(message_id: str) -> bool`
Delete a message from Twilio.
    ```python
    deleted = client.delete_sms("SMxxxxxxxxxxxxxxxxx")
    print(f"Deleted: {deleted}")
    ```
Parameter | Type | Description  
---|---|---  
`message_id` | `str` | The Twilio message SID  
**Returns:** `True` on success
**Raises:** `RequestFailed` on error
###  Media Handling 
####  `retrieve_media_list(message_id: str) -> Iterator[Media]`
Get all media attachments for a message.
    ```python
    for media in client.retrieve_media_list("SMxxxxxxxxxxxxxxxxx"):
        print(f"Media SID: {media.sid}")
        print(f"Content Type: {media.content_type}")
    ```
Parameter | Type | Description  
---|---|---  
`message_id` | `str` | The Twilio message SID  
**Returns:** Iterator of `Media` objects
**Raises:** `RequestFailed` on error
####  `retrieve_raw_media(message_id: str, media_sid: str) -> bytes`
Download the raw binary content of a media attachment.
    ```python
    for media in client.retrieve_media_list(message_sid):
        content = client.retrieve_raw_media(message_sid, media.sid)
        # Save to file
        with open(f"media_{media.sid}.jpg", "wb") as f:
            f.write(content)
    ```
Parameter | Type | Description  
---|---|---  
`message_id` | `str` | The Twilio message SID  
`media_sid` | `str` | The Twilio media SID  
**Returns:** Raw binary content (`bytes`)
**Raises:** `RequestFailed` on error
##  Data Structures 
###  Settings 
Configuration for the SmsClient.
Field | Type | Description  
---|---|---  
`account_sid` | `str` | Twilio Account SID  
`key` | `str` | Twilio API Key SID  
`secret` | `str` | Twilio API Key Secret  
###  SmsMms 
Represents an SMS or MMS message to send.
Field | Type | Description  
---|---|---  
`number_from` | `str` | Sender phone number (E.164 format)  
`number_from_sid` | `str` | Twilio SID of the sender phone number  
`number_to` | `str` | Recipient phone number (E.164 format)  
`message` | `str` | Text content of the message  
`media_url` | `str` | URL of media to attach (empty for SMS)  
`status_callback_url` | `str` | URL to receive delivery status updates  
###  Message 
Represents a Twilio message with full metadata.
Field | Type | Description  
---|---|---  
`sid` | `str` | Unique message identifier  
`body` | `str` | Message text content  
`date_created` | `datetime` | When the message was created  
`date_sent` | `datetime \| None` | When the message was sent  
`date_updated` | `datetime` | When the message was last updated  
`direction` | `MessageDirection` | Message direction  
`number_from` | `str` | Sender phone number  
`number_to` | `str` | Recipient phone number  
`price` | `str \| None` | Cost of the message  
`price_unit` | `str` | Currency of the price  
`error_code` | `int \| None` | Error code if failed  
`error_message` | `str \| None` | Error description if failed  
`uri` | `str` | API URI for this resource  
`count_media` | `int \| None` | Number of media attachments  
`count_segments` | `int` | Number of SMS segments  
`status` | `MessageStatus` | Current message status  
`sub_resource_uris` | `dict[str, str] \| None` | URIs to related resources  
###  Phone 
Represents a Twilio phone number with configuration.
Field | Type | Description  
---|---|---  
`account_sid` | `str` | Twilio Account SID  
`capabilities` | `Capabilities` | Phone capabilities (SMS, MMS, etc.)  
`date_created` | `datetime` | When added to account  
`date_updated` | `datetime` | Last configuration update  
`friendly_name` | `str` | User-defined name  
`phone_number` | `str` | Phone number in E.164 format  
`sid` | `str` | Unique phone number identifier  
`sms_fallback_method` | `HttpMethod` | HTTP method for fallback URL  
`sms_fallback_url` | `str` | Fallback URL if primary fails  
`sms_method` | `HttpMethod` | HTTP method for SMS webhook  
`sms_url` | `str` | Webhook URL for inbound SMS  
`status_callback_method` | `HttpMethod` | HTTP method for status callbacks  
`status_callback` | `str` | URL for status updates  
`status` | `str` | Current phone number status  
###  Capabilities 
Phone number communication capabilities.
Field | Type | Description  
---|---|---  
`fax` | `bool` | Supports fax  
`mms` | `bool` | Supports MMS (multimedia)  
`sms` | `bool` | Supports SMS (text)  
`voice` | `bool` | Supports voice calls  
###  Media 
Represents media attached to a message.
Field | Type | Description  
---|---|---  
`sid` | `str` | Unique media identifier  
`content_type` | `str` | MIME type (e.g., `image/jpeg`)  
`date_created` | `datetime` | When the media was created  
`date_updated` | `datetime` | When the media was last updated  
`parent_sid` | `str` | Message SID this media belongs to  
`uri` | `str` | API URI for this resource  
###  StatusInbound 
Parsed data from an inbound message webhook callback.
Field | Type | Description  
---|---|---  
`account_sid` | `str` | Twilio Account SID  
`message_sid` | `str` | Message SID  
`messaging_service_sid` | `str` | Messaging Service SID  
`sms_message_sid` | `str` | SMS Message SID  
`sms_sid` | `str` | SMS SID  
`sms_status` | `MessageStatus` | Message status  
`to_country` | `str` | Recipient country  
`to_zip` | `str` | Recipient ZIP code  
`to_state` | `str` | Recipient state  
`to_city` | `str` | Recipient city  
`from_country` | `str` | Sender country  
`from_zip` | `str` | Sender ZIP code  
`from_state` | `str` | Sender state  
`from_city` | `str` | Sender city  
`number_to` | `str` | Recipient phone number  
`number_from` | `str` | Sender phone number  
`body` | `str` | Message text  
`count_media` | `int` | Number of media attachments  
`count_segments` | `int` | Number of SMS segments  
`media_content_type` | `list[str]` | MIME types of attached media  
`media_url` | `list[str]` | URLs of attached media  
**Class Methods:**
Method | Description  
---|---  
`StatusInbound.callback_inbound(raw_body)` | Parse URL-encoded webhook body  
###  StatusOutboundApi 
Parsed data from an outbound message status callback.
Field | Type | Description  
---|---|---  
`account_sid` | `str` | Twilio Account SID  
`message_sid` | `str` | Message SID  
`sms_sid` | `str` | SMS SID  
`sms_status` | `MessageStatus` | SMS status  
`message_status` | `MessageStatus` | Message status  
`number_to` | `str` | Recipient phone number  
`number_from` | `str` | Sender phone number  
**Class Methods:**
Method | Description  
---|---  
`StatusOutboundApi.callback_outbound_api(raw_body)` | Parse URL-encoded webhook body  
###  TwiMlMessage 
Generates TwiML XML for responding to inbound messages.
Field | Type | Description  
---|---|---  
`number_to` | `str` | Recipient (optional in response)  
`number_from` | `str` | Sender (optional in response)  
`status_callback_url` | `str` | Status callback URL  
`message_text` | `str` | Message text content  
`media_url` | `str` | Media URL to attach  
`method` | `HttpMethod\|None` | HTTP method for callbacks  
**Class Methods:**
Method | Description  
---|---  
`TwiMlMessage.instance(message_text) -> TwiMlMessage` | Create text-only TwiML message  
`TwiMlMessage.instance_with_media(message_text, media_url) -> TwiMlMessage` | Create TwiML with media  
**Instance Methods:**
Method | Description  
---|---  
`to_xml() -> str` | Generate TwiML XML string  
**Example:**
    ```python
    # Simple text reply
    reply = TwiMlMessage.instance("Thanks for your message!")
    xml = reply.to_xml()
    # Reply with media
    reply = TwiMlMessage.instance_with_media("Check this out!", "https://example.com/image.jpg")
    xml = reply.to_xml()
    ```
##  Constants (Enums) 
###  MessageStatus 
Message lifecycle status values.
Value | Description  
---|---  
`ACCEPTED` | Message accepted by Twilio  
`SCHEDULED` | Message scheduled for future delivery  
`CANCELED` | Scheduled message was canceled  
`QUEUED` | Message queued for sending  
`SENDING` | Message is being sent  
`SENT` | Message sent to carrier  
`FAILED` | Message failed to send  
`DELIVERED` | Message delivered to recipient  
`UNDELIVERED` | Message could not be delivered  
`PARTIALLY_DELIVERED` | Some recipients received the message  
`RECEIVING` | Inbound message being received  
`RECEIVED` | Inbound message received  
`READ` | Message was read (WhatsApp only)  
###  MessageDirection 
Message direction types.
Value | Description  
---|---  
`INBOUND` | Message received from external number  
`OUTBOUND_API` | Message sent via API  
`OUTBOUND_CALL` | Message sent during a call  
`OUTBOUND_REPLY` | Message sent as webhook reply  
###  DateOperation 
Date filtering operations for message queries.
Value | Description  
---|---  
`ON_EXACTLY` | Messages on exactly this date  
`ON_AND_BEFORE` | Messages on or before this date  
`ON_AND_AFTER` | Messages on or after this date  
###  HttpMethod 
HTTP methods for webhook configuration.
Value | Description  
---|---  
`GET` | HTTP GET method  
`POST` | HTTP POST method  
##  Error Handling 
###  RequestFailed 
Exception raised when a Twilio API request fails (extends `RuntimeError`).
Attribute | Type | Description  
---|---|---  
`status_code` | `int` | HTTP status code  
`message` | `str` | Error message from Twilio  
**Example:**
    ```python
    try:
        message = client.send_sms_mms(sms)
    except RequestFailed as e:
        if e.status_code == 0:
            # Client-side validation error (e.g., phone lacks MMS capability)
            print(f"Validation error: {e.message}")
        else:
            # Twilio API error
            print(f"API error {e.status_code}: {e.message}")
    ```
##  Complete Webhook Example 
Here's a complete example of handling inbound SMS and sending replies:
    ```python
    from canvas_sdk.clients.twilio.structures import StatusInbound, TwiMlMessage
    def handle_webhook(raw_body: str) -> str:
        """
        Handle incoming SMS webhook from Twilio.
        Args:
            raw_body: URL-encoded form data from Twilio POST request
        Returns:
            TwiML XML response string
        """
        # Parse the inbound message
        inbound = StatusInbound.callback_inbound(raw_body)
        # Log the message
        print(f"From: {inbound.number_from}")
        print(f"To: {inbound.number_to}")
        print(f"Body: {inbound.body}")
        print(f"Media count: {inbound.count_media}")
        # Check for media attachments
        if inbound.count_media > 0:
            for i, url in enumerate(inbound.media_url):
                print(f"Media {i}: {inbound.media_content_type[i]} - {url}")
        # Generate appropriate response
        body_lower = inbound.body.lower()
        if "help" in body_lower:
            reply = TwiMlMessage.instance("Commands: HELP, STATUS, HELLO")
        elif "hello" in body_lower:
            reply = TwiMlMessage.instance_with_media(
                "Hello! Here's a welcome image!",
                "https://example.com/welcome.jpg"
            )
        elif "status" in body_lower:
            reply = TwiMlMessage.instance("System is operational.")
        else:
            reply = TwiMlMessage.instance("Unknown command. Text HELP for options.")
        return reply.to_xml()
    ```
##  Additional Resources 
  - [Twilio SMS API Documentation](https://www.twilio.com/docs/sms)
  - [Twilio Webhooks Guide](https://www.twilio.com/docs/messaging/guides/webhook-request)
  - [TwiML Reference](https://www.twilio.com/docs/messaging/twiml)
  - [Example Plugin](/sdk/example-twilio_sms_mms/) \- Documentation for the example plugin
  - [Source Code](https://github.com/canvas-medical/canvas-plugins/tree/main/example-plugins/twilio_sms_mms) \- View the source on GitHub
----- END PAGE https://docs.canvasmedical.com/sdk/clients-twilio/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/clients/
The clients module provides pre-built integrations with popular third-party services, letting your plugins send emails, SMS messages, interact with AI models, process documents, and manage cloud storage. Each client handles authentication, request formatting, and response parsing so you can focus on your plugin's logic.
All clients follow a consistent pattern: configure credentials via [plugin secrets](/sdk/secrets/), instantiate a client, and call methods. Error handling is standardized with a `RequestFailed` exception across most clients.
> **Warning:** When using third-party clients with your own API keys, you are responsible for all privacy, security, and regulatory compliance associated with those services. For certain providers such as OpenAI and Anthropic, you may contact Canvas to inquire about access through our compliant accounts. 
[ AWS S3 Upload, download, and manage files in Amazon S3. ](/sdk/clients-aws-s3/) [ Canvas FHIR Interact with the Canvas FHIR API for resources like Coverages and DocumentReferences. ](/sdk/clients-canvas-fhir/) [ Extend AI Intelligent document processing with extraction, classification, and splitting. ](/sdk/clients-extend-ai/) [ LLMs Unified interface for OpenAI, Anthropic, and Google AI models. ](/sdk/clients-llms/) [ SendGrid Send emails, manage webhooks, and track delivery with SendGrid. ](/sdk/clients-sendgrid/) [ Twilio Send SMS/MMS messages and manage phone numbers with Twilio. ](/sdk/clients-twilio/)
----- END PAGE https://docs.canvasmedical.com/sdk/clients/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/command-metadata-create-form-effect/
##  Overview 
The `CommandMetadataCreateFormEffect` allows developers to dynamically display additional fields with a command in a note. The values entered in these fields are stored as [command metadata](/sdk/data-command/#commandmetadata) against the target `command_uuid`.
The effect is returned from a handler that responds to the `COMMAND__FORM__GET_ADDITIONAL_FIELDS` event.
    ```python
    from canvas_sdk.effects.command_metadata import (
        CommandMetadataCreateFormEffect,
        FormField,
        InputType,
    )
    CommandMetadataCreateFormEffect(
        command_uuid="command-uuid",
        form_fields=[
            FormField(
                key="reason",
                label="Reason",
                type=InputType.SELECT,
                options=["Routine", "Follow-up", "Other"],
            ),
        ],
    )
    ```
##  Structure 
###  **FormField**
A FormField consists of the following properties:
####  Attributes 
Attribute | Type | Description  
---|---|---  
`key` | `str` | unique identifier of the field - command metadata key  
`label` | `str` | the label that will be displayed on the field  
`type` | `InputType` | the type of the input - TEXT, SELECT, DATE.  
`required` | `bool` | if the input is required.  
`editable` | `bool` | if the input can be editabled.  
`options` | `list[str]` | possible options for when the input type is set to "SELECT"  
`value` | `str` | default value used only when no CommandMetadata row exists for this key. If the user has previously saved a value (including a cleared/empty value) the stored row wins and this field is ignored.  
###  **CommandMetadataCreateFormEffect**
A CommandMetadataCreateFormEffect consists of the following properties:
####  Attributes 
Attribute | Type | Description  
---|---|---  
`command_uuid` | `str` | the UUID of the command these fields should be rendered on.  
`form_fields` | `list[FormField]` | list of fields.  
##  Validation 
The effect validates inputs before it is applied:
  - `command_uuid` is required.
  - `options` may only be set on fields whose `type` is `InputType.SELECT`; providing `options` on a `TEXT` or `DATE` field raises a validation error.
  - Every `key` must be unique across `form_fields`. Duplicates raise a validation error per duplicated key.
##  Example Usage 
The following handler declares two extra fields on every plan command when the platform requests additional fields for it:
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.command_metadata import (
        CommandMetadataCreateFormEffect,
        FormField,
        InputType,
    )
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class PlanCommandAdditionalFields(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.COMMAND__FORM__GET_ADDITIONAL_FIELDS)
        def compute(self) -> list[Effect]:
            # Only respond for plan commands.
            if self.event.context.get("schema_key") != "plan":
                return []
            form = CommandMetadataCreateFormEffect(
                command_uuid=self.event.target.id,
                form_fields=[
                    FormField(
                        key="priority",
                        label="Priority",
                        type=InputType.SELECT,
                        options=["low", "medium", "high"],
                    ),
                    FormField(
                        key="follow_up_date",
                        label="Follow-up date",
                        type=InputType.DATE,
                        editable=True,
                    ),
                ],
            )
            return [form.apply()]
    ```
Once the user fills out these fields, their values are persisted as command metadata and can be read back through the SDK [command metadata](/sdk/data-command/#commandmetadata) table.
##  Rendering on the printed note 
The same `COMMAND__FORM__GET_ADDITIONAL_FIELDS` event fires when a command is rendered for printing (single-command print URL or full note printout). The platform uses the response to label and order the fields shown beneath each command in the printed output.
Two things differ from the chart-form render path:
  - **Values come from stored command metadata, not from`FormField.value`.** The platform pairs each field declared in your effect with the matching `CommandMetadata` row by `key`. Whatever value is on `FormField` is ignored during print rendering. You do not need to populate `value` for print.
  - **Fields with no stored value or a blank value are skipped.** Only fields the user actually filled in will appear in the printout.
  - **Fields you do not declare are hidden.** A `CommandMetadata` row whose `key` is not in your response will not print, even if it exists in the database. This matches the chart UI: removing or renaming a key in your effect makes the prior data invisible.
###  Branching on `purpose`
The event context carries a `purpose` key indicating which call site triggered the request:
Value | When  
---|---  
`"form"` | Chart UI is rendering the command's edit form (default).  
`"print"` | Single-command printout or note printout is being generated.  
Read it from the handler context to vary your response — for example, to omit internal fields from print, or shorten labels for a denser layout.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.command_metadata import (
        CommandMetadataCreateFormEffect,
        FormField,
        InputType,
    )
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class PlanCommandAdditionalFields(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.COMMAND__FORM__GET_ADDITIONAL_FIELDS)
        def compute(self) -> list[Effect]:
            if self.event.context.get("schema_key") != "plan":
                return []
            is_print = self.event.context.get("purpose") == "print"
            fields = [
                FormField(
                    key="priority",
                    label="Priority",
                    type=InputType.SELECT,
                    options=["low", "medium", "high"],
                ),
                FormField(
                    key="follow_up_date",
                    label="Follow-up date",
                    type=InputType.DATE,
                ),
            ]
            if not is_print:
                # Internal-only: visible on the form, hidden from printouts.
                fields.append(
                    FormField(
                        key="reviewer_notes",
                        label="Reviewer notes",
                        type=InputType.TEXT,
                    )
                )
            return [
                CommandMetadataCreateFormEffect(
                    command_uuid=self.event.target.id,
                    form_fields=fields,
                ).apply()
            ]
    ```
###  Tips for the print path 
  - **Use clear, human-readable`label` values.** Whatever you put on `FormField.label` is what the printed output shows. The platform does not derive a label from the `key`.
  - **Use the same`key` you used when persisting metadata.** The print path joins on `key`; mismatches mean nothing renders for that field.
  - **`type`, `options`, and `required` are ignored at print time.** Only `key` and `label` shape the printout.
  - **Need to retire a field?** Removing it from the print response hides it for all future prints — including for committed notes. If you need the historical value to keep showing on signed records, keep the field declared (or declare it only when `purpose == "print"`).
----- END PAGE https://docs.canvasmedical.com/sdk/command-metadata-create-form-effect/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/commands-custom-command/
##  Introduction 
The `CustomCommand` class allows plugins to create custom commands with HTML-rendered content that can be inserted into patient charts. Custom commands are designed for displaying read-only content and do not support user input or interactive forms.
**Important** : Custom commands must be configured in the plugin's `CANVAS_MANIFEST.json` file under the `commands` array before they can be used.
##  Parameters 
Name | Type | Required | Description  
---|---|---|---  
`note_uuid` | _string_ | `true` | The externally exposable id of the note in which to insert the command.  
`command_uuid` | _string_ | `true` | The externally exposable id of the command which is being referenced.  
`schema_key` | _string_ | `true` | Identifier for data binding. Must match the `schema_key` in your manifest configuration and must be unique across every plugin installed on the instance.  
`content` | _string_ | `true` | HTML content for display in the chart.  
`print_content` | _string_ | `false` | HTML content for print version (recommended for optimal print output).  
##  Manifest Configuration 
Custom commands must be declared in your `CANVAS_MANIFEST.json`:
    ```json
    {
      "components": {
        "commands": [
          {
            "name": "RiskAssessment",
            "label": "Risk Assessment",
            "schema_key": "riskAssessment",
            "section": "assessment"
          }
        ]
      }
    }
    ```
###  Manifest Fields 
  - **name** : Unique name for the command
  - **label** : User-friendly label displayed in Canvas UI
  - **schema_key** : Identifier for the command. Must be unique across every plugin installed on the Canvas instance — if another installed plugin already declares the same `schema_key`, installation will be rejected. CustomCommand instances must use this value.
  - **section** : Chart section where command appears: `subjective`, `objective`, `assessment`, `plan`, `procedures`, `history`, or `internal`
**Note** : `schema_key` values must be unique across **all plugins installed on the instance** , not just within a single plugin. If you install a plugin whose manifest declares a `schema_key` already owned by another installed plugin, the installation fails with a clear validation error instead of silently overwriting the existing command. Choose a distinctive `schema_key` — for example, prefixing it with your plugin's name — to avoid collisions.
##  Basic Usage 
###  Step 1: Create HTML Templates 
Create a template file for your command content (e.g., `templates/risk_assessment.html`):
    ```html
    <!DOCTYPE html>
    <html>
    <head>
        <style>
            .risk-assessment {
                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
                padding: 16px;
            }
            .risk-header {
                font-size: 18px;
                font-weight: 600;
                margin-bottom: 16px;
            }
            .risk-item {
                padding: 8px;
                margin-bottom: 8px;
                background: #f7fafc;
                border-radius: 4px;
            }
            .risk-level {
                font-weight: 600;
                color: #c53030;
            }
        </style>
    </head>
    <body>
        <div class="risk-assessment">
            <div class="risk-header">Risk Assessment</div>
            <div class="risk-item">
                <span class="risk-level">Cardiovascular Risk: High</span>
                <p>Hypertension, family history of heart disease</p>
            </div>
            <div class="risk-item">
                <span class="risk-level">Falls Risk: Moderate</span>
                <p>Age over 65, history of dizziness</p>
            </div>
        </div>
    </body>
    </html>
    ```
Create a simpler print version (e.g., `templates/risk_assessment_print.html`):
    ```html
    <!DOCTYPE html>
    <html>
    <head>
        <style>
            .risk-assessment-print {
                font-family: Arial, sans-serif;
                font-size: 11px;
                .section {
                    margin-bottom: 8px;
                }
                .label {
                    font-weight: bold;
                }
            }
        </style>
    </head>
    <body class="risk-assessment-print">
        <h3>Risk Assessment</h3>
        <div class="section">
            <span class="label">Cardiovascular Risk:</span> High - Hypertension, family history
        </div>
        <div class="section">
            <span class="label">Falls Risk:</span> Moderate - Age over 65, history of dizziness
        </div>
    </body>
    </html>
    ```
###  Step 2: Use Templates in Your Command 
    ```python
    from canvas_sdk.commands.commands.custom_command import CustomCommand
    from canvas_sdk.templates import render_to_string
    import uuid
    command = CustomCommand(
        schema_key="riskAssessment",
        content=render_to_string("templates/risk_assessment.html"),
        print_content=render_to_string("templates/risk_assessment_print.html")
    )
    command.command_uuid = str(uuid.uuid4())
    command.note_uuid = "rk786p"
    effect = command.originate()
    ```
###  Extended CustomCommand Class (For Reusability) 
Create a subclass with a predefined `schema_key`:
    ```python
    from canvas_sdk.commands.commands.custom_command import CustomCommand
    from canvas_sdk.templates import render_to_string
    import uuid
    class RiskAssessmentCommand(CustomCommand):
        """Custom command for risk assessment."""
        class Meta:
            schema_key = "riskAssessment"
    # Usage
    command = RiskAssessmentCommand(
        content=render_to_string("templates/risk_assessment.html"),
        print_content=render_to_string("templates/risk_assessment_print.html")
    )
    command.command_uuid = str(uuid.uuid4())
    command.note_uuid = "rk786p"
    effect = command.originate()
    ```
##  Methods 
###  originate() 
Returns an Effect that originates a new command in the note body.
**Example:**
    ```python
    from canvas_sdk.commands.commands.custom_command import CustomCommand
    from canvas_sdk.templates import render_to_string
    import uuid
    command = CustomCommand(
        schema_key="riskAssessment",
        content=render_to_string("templates/risk_assessment.html"),
        print_content=render_to_string("templates/risk_assessment_print.html")
    )
    command.command_uuid = str(uuid.uuid4())
    command.note_uuid = "rk786p"
    effect = command.originate()
    ```
##  Content vs Print Content 
Custom commands support two versions of content:
###  Display Content (content) 
  - Rendered in the Canvas UI when viewing the chart
  - Can include rich styling and complex layouts
###  Print Content (print_content) 
  - Rendered when printing the chart or generating PDFs
  - Should be simpler and more compact
**Best Practice** : Always provide both versions for the best user experience.
##  Limitations 
  - Custom commands are read-only and cannot capture user input
  - Interactive elements (forms, buttons, input fields) are not supported
  - Commands must be configured in the manifest before use
  - The `schema_key` must be unique across every plugin installed on the Canvas instance. Installing a plugin whose `schema_key` is already owned by another installed plugin will fail with a validation error.
----- END PAGE https://docs.canvasmedical.com/sdk/commands-custom-command/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/commands/
The commands module lets you create and update commands within a specific note in Canvas. Commands are the building blocks of many end-user workflows in Canvas, including nearly all clinical workflows for documentation, like HPIs and questionnaires, as well as orders like prescriptions, labs, and referrals. Each Command class can be instantiated in your plugin and used to build a new command instance within a specific note or update an existing instance. The commands are then displayed in real time within the end user's workflow.
Common objectives that can be met by using Command classes include dynamic note templates, clinical decision support, order set composition, care gap closure, and care coordination automation.
Commands are written from an event handler by default. To let something outside Canvas write them — a patient-facing form, a device, an internal tool — expose them over HTTP with [`CommandAPI`](/sdk/handlers-simple-api-commands/), which reads a request body onto any command on this page, validates it, and emits the effects. The [Writing Commands Over HTTP](/guides/writing-commands-over-http/) guide walks through building one.
> **Info:** New to command fields? Fields that are autocompletes, dropdowns, or enums in the Canvas UI take a raw code, id, or enum value in the SDK — you have to look the value up first. See [Populating Command Fields](/guides/populating-command-fields/) for where each value comes from. 
##  Common Attributes 
###  Parameters 
All commands share the following init kwarg parameters:
Name | Type | Required | Description  
---|---|---|---  
`note_uuid` | _string_ | `true` if creating a new command | The id of the [Note](/sdk/data-note/#note) in which to insert the command.  
`command_uuid` | _string_ | `true` if updating an existing command | The id of the [Command](/sdk/data-command/#command). On `originate` you can pass your own value to set it the first time; when updating, it references an existing command.  
All parameters can be set upon initialization, and also updated on the class instance.
Field values are read leniently, so a value does not have to arrive already in the field's own type: a number can be given as `"3"`, a date as `"2026-08-04"`, and an enum as its value (`"mild"`) rather than the member. This matters most when the values come from somewhere that only has strings, such as a JSON request body.
###  Methods 
**Not every command supports every method.** `originate` is the only one they all have; `edit`, `delete`, `commit`, `enter_in_error`, `review`, `send`, `delegate` and `sign` each depend on the command. The [command type table](/sdk/effects/#commands) lists the actions each command accepts — check it before relying on one. `upsert_metadata` works on any command, and `set_custom_html` belongs to [custom commands](/sdk/commands-custom-command/) alone.
To call these over HTTP rather than from a handler, see [`CommandAPI`](/sdk/handlers-simple-api-commands/#methods).
####  originate 
Returns an Effect that originates a new command in the note body.
**Parameters:**
Name | Type | Required | Default | Description  
---|---|---|---|---  
`commit` | `bool` | No | `False` | When `True`, the command is automatically committed after origination. This is a simpler alternative to returning separate `originate()` and `commit()` effects. **Note:** This only applies to command types that support the COMMIT action. Commands that do not support committing (Reason For Visit, Prescribe, Refill, Adjust Prescription, Refer, and Order commands) will ignore this parameter. See the [command type table](/sdk/effects/#commands) for which commands support COMMIT.  
`line_number` | `int` | No | `-1` | The line number in the note where the command should be inserted. By default the command will insert at the bottom of the note.  
**See also:** For efficiently inserting multiple commands at once, see [Batch Originate Commands](/sdk/effect-batch-originate/).
**Examples** :
    ```python
    from canvas_sdk.commands import PlanCommand
    def compute():
        new_plan = PlanCommand(note_uuid='8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47', narrative='new')
        new_plan.narrative = 'newer'
        return [new_plan.originate()]
    ```
To originate and commit in a single effect:
    ```python
    from canvas_sdk.commands import DiagnoseCommand
    def compute():
        diagnose_command = DiagnoseCommand(
            note_uuid='550e8400-e29b-41d4-a716-446655440000',
            icd10_code='E11.9'
        )
        return [diagnose_command.originate(commit=True)]
    ```
####  edit 
Returns an Effect that edits an existing command with the values set on the command class instance.
**Behavior and Considerations:**
  - **Partial Edits:** If you update only some fields of the command, any fields not explicitly modified will retain their existing values.
  - **No Changes:** Calling `edit()` without making any changes will result in a no-op; the command remains unchanged.
  - **Invalid Values:** If you attempt to set an invalid value, you should receive a validation error.
**Example** :
    ```python
    from canvas_sdk.commands import PlanCommand
    def compute():
        existing_plan = PlanCommand(command_uuid='2b9d1f0a-4c3e-4b5a-9d8c-7e6f5a4b3c2d', narrative='something new')
        return [existing_plan.edit()]
    ```
####  delete 
Returns an Effect that deletes an existing, non-committed command from the note body.
**Example** :
    ```python
    from canvas_sdk.commands import PlanCommand
    def compute():
        existing_plan = PlanCommand(command_uuid='2b9d1f0a-4c3e-4b5a-9d8c-7e6f5a4b3c2d')
        return [existing_plan.delete()]
    ```
####  commit 
Returns an Effect that commits an existing, non-committed command to the note body.
To block a command from committing and surface a message to the user — for example, enforcing your own business rules before a command is entered — return a [Command Validation effect](/sdk/effect-command-validation/) from a handler on the command's validation event.
**Example** :
    ```python
    from canvas_sdk.commands import PlanCommand
    def compute():
        existing_plan = PlanCommand(command_uuid='2b9d1f0a-4c3e-4b5a-9d8c-7e6f5a4b3c2d')
        return [existing_plan.commit()]
    ```
####  review 
Returns an Effect that sets a command in review.
**Limited availability** The `review()` method can only be called on Prescribe commands objects. Other command types do not support this operation.
**Example** :
    ```python
    from canvas_sdk.commands import PrescribeCommand
    def compute():
        existing_prescribe = PrescribeCommand(command_uuid='e32b85d9-ccb7-4e4f-a0e5-8783ed2d9528')
        return [existing_prescribe.review()]
    ```
####  send 
Returns an Effect that sends a signed command.
**Limited availability** The `send()` method can only be called on LabOrder, Prescribe, Refill and AdjustPrescription command objects. Other command types do not support this operation. The three prescribing commands share one set of electronic prescribing validations.
**Parameters:**
Name | Type | Required | Default | Description  
---|---|---|---|---  
`practice_location_override` | `str \| UUID` | No | `None` | Prescribe only. The `id` of a [PracticeLocation](/sdk/data-practicelocation/#practicelocation) whose address is used as the prescriber address on the outgoing prescription, overriding the prescriber's primary location. See Prescribe for behavior and limitations.  
**Example** :
    ```python
    from canvas_sdk.commands import PrescribeCommand
    def compute():
        existing_prescribe = PrescribeCommand(command_uuid='e32b85d9-ccb7-4e4f-a0e5-8783ed2d9528')
        return [existing_prescribe.send()]
    ```
To send the prescription using a specific practice location's address (see Prescribe):
    ```python
    from canvas_sdk.commands import PrescribeCommand
    def compute():
        existing_prescribe = PrescribeCommand(command_uuid='e32b85d9-ccb7-4e4f-a0e5-8783ed2d9528')
        return [existing_prescribe.send(practice_location_override='a1b2c3d4-e5f6-7890-abcd-ef1234567890')]
    ```
####  enter_in_error 
Returns an effect that enter-in-errors an existing, committed command in the note body.
**Example** :
    ```python
    from canvas_sdk.commands import PlanCommand
    def compute():
        existing_plan = PlanCommand(command_uuid='2b9d1f0a-4c3e-4b5a-9d8c-7e6f5a4b3c2d')
        return [existing_plan.enter_in_error()]
    ```
####  delegate 
Returns an Effect that delegates an existing, staged command by creating a task.
**Limited availability** The `delegate()` method can only be called on ImagingOrder and Refer command objects. Other command types do not support this operation.
**Example** :
    ```python
    from canvas_sdk.commands import ReferCommand
    def compute():
        existing_refer = ReferCommand(command_uuid='e32b85d9-ccb7-4e4f-a0e5-8783ed2d9528')
        return [existing_refer.delegate()]
    ```
####  sign 
Returns an Effect that signs an existing, staged command, transitioning it to a committed state.
**Limited availability** The `sign()` method can only be called on ImagingOrder and Refer command objects. Other command types do not support this operation.
**Example** :
    ```python
    from canvas_sdk.commands import ImagingOrderCommand
    def compute():
        existing_imaging_order = ImagingOrderCommand(command_uuid='e32b85d9-ccb7-4e4f-a0e5-8783ed2d9528')
        return [existing_imaging_order.sign()]
    ```
####  upsert_metadata 
Returns a [CommandMetadata effect](/sdk/effect-command-metadata/) that creates or updates a metadata key-value pair on a command. If metadata with the given key already exists on the command, its value will be updated. Otherwise, a new metadata record will be created.
The `command_uuid` field must be set on the command object before calling `upsert_metadata`.
To make this metadata **visible and editable as fields on the command in the note** — rather than only stored behind the scenes — use the [Command Metadata Create Form effect](/sdk/command-metadata-create-form-effect/), which renders additional fields on the command whose values are saved as command metadata.
Parameter | Type | Description  
---|---|---  
`key` | _string_ | The metadata key (max 256 characters).  
`value` | _string_ | The metadata value.  
**Example** :
    ```python
    from canvas_sdk.commands import PlanCommand
    def compute():
        existing_plan = PlanCommand(command_uuid='2b9d1f0a-4c3e-4b5a-9d8c-7e6f5a4b3c2d')
        return [existing_plan.upsert_metadata(key="priority", value="high")]
    ```
####  set_custom_html 
Returns an effect that sets or clears custom HTML content on a command. The HTML is stored on the command and rendered alongside it in the note.
The `command_uuid` field must be set on the command object before calling `set_custom_html`. The command must be in a staged (not committed) state—calling this method on a committed command will raise a validation error.
Parameter | Type | Description  
---|---|---  
`custom_html` | _string_ or _None_ | The HTML content to set on the command, or `None` to clear it.  
**Example** :
    ```python
    from canvas_sdk.commands import PlanCommand
    def compute():
        existing_plan = PlanCommand(command_uuid='2b9d1f0a-4c3e-4b5a-9d8c-7e6f5a4b3c2d')
        return [existing_plan.set_custom_html("<div class='highlight'>Important note</div>")]
    ```
To clear existing custom HTML from a command:
    ```python
    from canvas_sdk.commands import PlanCommand
    def compute():
        existing_plan = PlanCommand(command_uuid='2b9d1f0a-4c3e-4b5a-9d8c-7e6f5a4b3c2d')
        return [existing_plan.set_custom_html(None)]
    ```
##  Originating and Committing Together 
The simplest way to originate and commit a command in a single plugin action is to pass `commit=True` to the `originate()` method:
    ```python
    from canvas_sdk.commands import DiagnoseCommand
    def compute():
        diagnose_command = DiagnoseCommand(
            note_uuid='550e8400-e29b-41d4-a716-446655440000',
            icd10_code='E11.9'
        )
        return [diagnose_command.originate(commit=True)]
    ```
This handles the origination and commit in a single effect, without needing to manage a `command_uuid` yourself.
###  Chaining Methods with a User-set UUID 
If you need more control over the process — for example, to edit a command between origination and commit — you can chain separate effects by setting the `command_uuid` manually. This is also required for questionnaire-based commands, where `originate()` creates the command but does not add the answers — you must chain an `edit()` to populate the responses (see Usage Example). This chaining is necessary because the `originate` method executes asynchronously, so there is no way to get the `command_uuid` back from the originate action and use it for subsequent actions in the same operation.
    ```python
    from uuid import uuid4
    from canvas_sdk.commands import DiagnoseCommand
    def compute():
        note_uuid = '550e8400-e29b-41d4-a716-446655440000'
        diagnose_command = DiagnoseCommand(
            note_uuid=note_uuid,
            icd10_code='E11.9'
        )
        # To chain command effects, you must know what the command's id
        # is. To accomplish that, we set the id ourselves rather than
        # allow the database to assign one.
        diagnose_command.command_uuid = str(uuid4())
        # Now we can both originate and commit in a single operation
        return [diagnose_command.originate(), diagnose_command.commit()]
    ```
This pattern ensures that both the originate and commit operations use the same `command_uuid`, allowing them to be chained together reliably in a single plugin execution.
Command-specific details for each command class can be found below.
##  Command Actions 
All commands support user-triggered actions through the Canvas UI. These actions appear as buttons or menu items that users can click to perform operations on a command.
Commands have two types of actions:
  - **Generic actions** — available on all commands (listed below).
  - **Command-specific actions** — vary by command type and are documented in each command's section below.
Action | Description  
---|---  
`print` | Generates a printable version of the command for documentation or external sharing.  
`audit_history` | Displays the complete audit trail for the command, showing all modifications, state changes, and user interactions over time.  
`carry_forward` | Populates the command with the last known data for this command type and patient, letting users quickly recreate a similar command from a previous entry.  
> **Info:** The send action is the only command action available through the SDK, and only LabOrder, Prescribe, Refill and Adjust Prescription commands support it. 
###  Customizing Action Availability 
You can programmatically control which actions appear on a command — and in what order — by responding to that command's `AVAILABLE_ACTIONS` event. Common uses:
  - **Hide actions** based on user permissions, role, or command state.
  - **Reorder actions** to prioritize commonly used operations.
  - **Conditionally show actions** depending on workflow or business logic.
**How it works:**
  1. When Canvas renders a command, it fires that command's `<COMMAND>_COMMAND__AVAILABLE_ACTIONS` event (e.g. `PLAN_COMMAND__AVAILABLE_ACTIONS`).
  2. Your handler receives the default action list in `self.context["actions"]` and the acting user in `self.context["user"]`.
  3. Return a single `COMMAND_AVAILABLE_ACTIONS_RESULTS` effect whose payload is the action list you want rendered. The returned list **replaces** the default set, so include every action the user should see — returning the original list unchanged is a no-op.
**Example** — hide the `print` action for a specific user:
    ```python
    import json
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.effects import Effect, EffectType
    from canvas_sdk.events import EventType
    from canvas_sdk.v1.data import Staff
    class Handler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.PLAN_COMMAND__AVAILABLE_ACTIONS)
        def compute(self) -> list[Effect]:
            actions = self.context["actions"]
            user_id = self.context["user"]["staff"]
            try:
                staff = Staff.objects.get(id=user_id)
                # Hide the print action for this user; everyone else keeps the full set
                if staff.first_name == "Larry":
                    filtered_actions = [a for a in actions if a["name"] != "print"]
                else:
                    filtered_actions = actions
            except Staff.DoesNotExist:
                filtered_actions = actions
            return [
                Effect(
                    type=EffectType.COMMAND_AVAILABLE_ACTIONS_RESULTS,
                    payload=json.dumps(filtered_actions),
                )
            ]
    ```
##  Command Validation 
Beyond the built-in validation each command performs on its own fields, you can add your **own** validation rules to a command and surface error messages to the user directly in the Canvas UI. A handler responds to a command's validation event (for example, `PLAN_COMMAND__POST_VALIDATION`) and returns a [Command Validation effect](/sdk/effect-command-validation/) containing one or more error messages. This is useful for enforcing organization-specific business rules — such as requiring a field, restricting certain combinations, or blocking a command until an external condition is met — before the command can be committed.
See the [Command Validation effect](/sdk/effect-command-validation/) documentation for the full API and examples.
##  Commands 
The sections below document each command class. See Common Attributes for the parameters and methods shared by all commands.
###  Custom Commands 
For creating custom commands with HTML-rendered content that can be inserted into patient charts, see the [CustomCommand](/sdk/commands-custom-command/) documentation.
Custom commands are different from standard commands:
  - They allow you to display read-only HTML content in the patient chart
  - They must be configured in your plugin's manifest before use
  - They support both display and print versions of content
  - They are designed for displaying formatted data, not for capturing user input
Learn more: [CustomCommand Reference](/sdk/commands-custom-command/)
* * *
###  AdjustPrescription 
**Command-specific parameters** :
Name | Type | Required to review / send | Description  
---|---|---|---  
`new_fdb_code` | _string_ | `true` | The [FDB code](/sdk/utils/#fdb_code) of the new medication.  
Check the Prescribe command for the other parameters used in the Adjust Prescription command. Adjust Prescription supports `send()` under the same electronic prescribing validations.
    ```python
    from canvas_sdk.commands import AdjustPrescriptionCommand, PrescribeCommand
    from canvas_sdk.commands.constants import ClinicalQuantity
    AdjustPrescriptionCommand(
        fdb_code="172480",
        new_fdb_code="216092",
        icd10_codes=["R51"],
        sig="Take one tablet daily after meals",
        days_supply=30,
        quantity_to_dispense=30,
        type_to_dispense=ClinicalQuantity(
            representative_ndc="12843016128",
            ncpdp_quantity_qualifier_code="C48542"
        ),
        refills=3,
        substitutions=PrescribeCommand.Substitutions.ALLOWED,
        pharmacy="pharmacy_ncpdp_id",
        prescriber_id="a7c2e9d1-3b4f-4a6c-8e0d-5f1a2b3c4d5e",
        supervising_provider_id="c3d4e5f6-7a8b-4c9d-0e1f-2a3b4c5d6e7f",
        note_to_pharmacist="Please verify patient's insurance before processing."
    )
    ```
* * *
###  Allergy 
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`allergy` | _Allergen_ | `false` | Represents the allergen. See details in the Allergen type below. Search allergens with the [ontologies allergen search](/sdk/utils/#get-fdballergy--full-text-search).  
`severity` | _Severity enum_ | `false` | The severity of the allergic reaction. Must be one of `AllergyCommand.Severity`.  
`narrative` | _string_ | `false` | A narrative or free-text description of the allergy (max length: 512 characters).  
`approximate_date` | _datetime_ | `false` | The approximate date the allergy was identified.  
**Enums and Types** :
**`Allergen`**
Attribute | Type | Description  
---|---|---  
`concept_id` | _integer_ | The identifier for the allergen concept.  
`concept_type` | _AllergenType enum_ | The type of allergen. See `AllergenType` values below.  
AllergenType | Value | Description  
---|---|---  
`ALLERGEN_GROUP` | `1` | Represents a group of allergens.  
`MEDICATION` | `2` | Represents a medication allergen.  
`INGREDIENT` | `6` | Represents an ingredient allergen.  
Severity | Value | Description  
---|---|---  
`MILD` | `"mild"` | Indicates a mild reaction.  
`MODERATE` | `"moderate"` | Indicates a moderate reaction.  
`SEVERE` | `"severe"` | Indicates a severe reaction.  
**Example** :
    ```python
    from canvas_sdk.commands.commands.allergy import AllergyCommand, AllergenType, Allergen
    from datetime import date
    allergy = AllergyCommand(
        note_uuid="8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47",
        allergy=Allergen(concept_id=12345, concept_type=AllergenType.MEDICATION),
        severity=AllergyCommand.Severity.SEVERE,
        narrative="Severe rash and difficulty breathing after penicillin.",
        approximate_date=date(2023, 6, 15)
    )
    ```
* * *
###  Assess 
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`condition_id` | _string_ | `true` | The id of the [Condition](/sdk/data-condition/#condition) being assessed. Must be a condition already recorded on that patient's chart.  
`background` | _string_ | `false` | Background information about the diagnosis.  
`status` | _Status enum_ | `false` | The current status of the diagnosis. Must be one of `AssessCommand.Status`.  
`narrative` | _string_ | `false` | The narrative for the current assessment (max 2048 characters; values exceeding the limit raise a validation error instead of being truncated).  
`Status` | Value | Description  
---|---|---  
`IMPROVED` | `"improved"` | The condition has improved.  
`STABLE` | `"stable"` | The condition is stable.  
`DETERIORATED` | `"deteriorated"` | The condition has deteriorated.  
**Example** :
    ```python
    from canvas_sdk.commands import AssessCommand
    assess = AssessCommand(
        note_uuid='8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47',
        condition_id='a1c2e3d4-5b6f-4a7c-8e9d-0f1a2b3c4d5e',
        background='started in 2012',
        status=AssessCommand.Status.STABLE,
        narrative='experiencing more pain lately'
    )
    ```
**Validation** :
`condition_id` must belong to the same patient as the note or command it is written to: the patient comes from `note_uuid` when you `originate` the command, and from the existing command when you `edit` one. A condition on another patient's chart — or an id that matches no condition at all — fails validation, and the command is neither created nor updated. This check is deferred when the target note (on `originate`) or command (on `edit`) is not yet persisted — for example, when a plugin creates the note and originates `AssessCommand`s against that same `note_uuid` in a single handler response. In that case the note's or command's patient cannot be resolved yet, so `condition_id` passes this validation. The patient-ownership check then runs later, once the command is applied and the note exists.
The check needs that note or command to exist, so it is skipped when you create the note and originate the command in the same batch of effects. Nothing is rejected in that case, since there is not yet a chart to compare the condition against.
* * *
###  ChangeMedication 
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`medication_id` | _string_ | `true` | The id of the [Medication](/sdk/data-medication/#medication) being changed. Must be an active medication on that patient's chart.  
`sig` | _string_ | `false` | Administration details of the medication.  
**Example** :
    ```python
    from canvas_sdk.commands.commands.change_medication import ChangeMedicationCommand
    change_medication = ChangeMedicationCommand(
        note_uuid='8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47',
        medication_id='f0a1b2c3-d4e5-4f6a-8b9c-0d1e2f3a4b5c',
        sig='two pills taken orally'
    )
    ```
**Validation** :
`medication_id` must belong to the same patient as the note or command it is written to: the patient comes from `note_uuid` when you `originate` the command, and from the existing command when you `edit` one. The medication must also be active. A medication on another patient's chart, an id that matches no medication, or an inactive medication fails validation, and the command is neither created nor updated. This check is deferred when the target note (on `originate`) or command (on `edit`) is not yet persisted — for example, when a plugin creates the note and originates the command in the same batch of handler effects. In that case the command's patient cannot be resolved yet, so `medication_id` passes this validation; the check then runs once the command is applied.
A malformed `medication_id` fails at command construction, before any patient lookup, while a well-formed UUID passed as a string is accepted.
* * *
###  ChartSectionReview 
Records that a section of the patient's chart was reviewed during a visit. Originating the command snapshots the patient's active records in that section onto the note, along with the rendered text of those records as they read at the time of review — the same thing that happens when a user clicks **Review** on a chart section in the Canvas UI. Use it to attest to a review your plugin has already performed, such as reconciling medications from an external source.
The command is always committed on origination, so there is no staged state to fill in and no need to pass `commit=True`.
Read the resulting snapshot back with the [ChartSectionReview](/sdk/data-chart-section-review/#chartsectionreview) data model.
> **Info:** This command supports `originate()` only since it is a read only command. 
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`section` | _ChartSectionReviewCommand.Sections enum_ | `true` | The chart section being reviewed. Required when instantiating the command. Must be one of `ChartSectionReviewCommand.Sections`.  
**Example** :
    ```python
    from canvas_sdk.commands import ChartSectionReviewCommand
    def compute():
        medication_review = ChartSectionReviewCommand(
            note_uuid="8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47",
            section=ChartSectionReviewCommand.Sections.MEDICATIONS,
        )
        return [medication_review.originate()]
    ```
####  ChartSectionReviewCommand.Sections 
Member | Value | Chart section  
---|---|---  
`CONDITIONS` | `conditions` | Conditions  
`SURGICAL_HISTORY` | `surgical_history` | Surgical History  
`MEDICATIONS` | `medications` | Medications  
`FAMILY_HISTORY` | `family_histories` | Family Histories  
`ALLERGIES` | `allergies` | Allergies  
`IMMUNIZATIONS` | `immunizations` | Immunizations  
> **Warning:** The member name for family history differs between the command and the data model: the command uses `ChartSectionReviewCommand.Sections.FAMILY_HISTORY`, while the data model uses `ChartSectionReviewSection.FAMILY_HISTORIES`. Both carry the same value, `family_histories`. 
* * *
###  CloseGoal 
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`goal_id` | _int_ | `true` | The `dbid` of the [Goal](/sdk/data-goal/#goal) being closed. Must be a goal on that patient's chart.  
`achievement_status` | _AchievementStatus enum_ | `false` | The final achievement status of the goal. Must be one of `GoalCommand.AchievementStatus`.  
`progress` | _string_ | `false` | A narrative about the patient's progress toward the goal.  
**Example** :
    ```python
    from canvas_sdk.commands import CloseGoalCommand, GoalCommand
    close_goal = CloseGoalCommand(
        note_uuid="8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47",
        goal_id=12345,
        achievement_status=GoalCommand.AchievementStatus.ACHIEVED,
        progress="Patient has achieved the target weight goal of 150 lbs."
    )
    ```
###  Diagnose 
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`icd10_code` | _string_ | `true` | ICD-10 code of the condition being diagnosed. Search with the [ICD-10 condition endpoint](/sdk/utils/#get-icdcondition--icd-10-conditions).  
`background` | _string_ | `false` | Background information about the diagnosis.  
`approximate_date_of_onset` | _datetime_ | `false` | The approximate date the condition began.  
`today_assessment` | _string_ | `false` | The narrative for the initial assessment of the condition (max length: 2048 characters).  
**Example** :
    ```python
    from canvas_sdk.commands import DiagnoseCommand
    from datetime import datetime
    diagnose = DiagnoseCommand(
        note_uuid='8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47',
        icd10_code='M54.50',
        background='lifted heavy box',
        approximate_date_of_onset=datetime(2012, 1, 1),
        today_assessment='unable to sleep lately'
    )
    ```
* * *
###  FamilyHistory 
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`family_history` | _string_ or _Coding_ | `true` | A description of the family history being documented. Search with the [family-history endpoint](/sdk/utils/#get-snomedfamily-history--family-history-conditions).  
`relative` | _string_ | `false` | A description of the relative (e.g., mother, uncle). Search with the [family-relation endpoint](/sdk/utils/#get-snomedfamily-relation--family-relationships).  
`note` | _string_ | `false` | Additional notes or context about the family history (max length: 512 characters).  
**Coding Support** :
The `family_history` parameter accepts either:
  - **String** : Searches for matching family history conditions and selects the first result.
  - **Coding object** : Allows structured or unstructured coding 
    - Supported systems: `SNOMED`, `UNSTRUCTURED`
    - Required fields: `system`, `code`
    - Optional field: `display`
The `relative` parameter also searches and selects the first result when a string is provided. Use specific terms (e.g., `"Paternal Grandfather"`, `"Maternal Grandfather"`) to avoid ambiguous matches.
**Example** :
    ```python
    from canvas_sdk.commands import FamilyHistoryCommand
    from canvas_sdk.commands.constants import CodeSystems, Coding
    # Using a string (searches and takes the first result — may be ambiguous)
    family_history = FamilyHistoryCommand(
        note_uuid="8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47",
        family_history="Diabetes Type 2",
        relative="Mother",
        note="Diagnosed at age 45"
    )
    # Using a SNOMED code
    family_history_snomed = FamilyHistoryCommand(
        note_uuid="8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47",
        family_history=Coding(
            system=CodeSystems.SNOMED,
            code="44054006",
            display="Diabetes Type 2"
        ),
        relative="Mother",
        note="Diagnosed at age 45"
    )
    # Using unstructured (free text)
    family_history_unstructured = FamilyHistoryCommand(
        note_uuid="8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47",
        family_history=Coding(
            system=CodeSystems.UNSTRUCTURED,
            code="Family history of heart disease"
        ),
        relative="Father"
    )
    ```
* * *
###  FollowUp 
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`structured` | _boolean_ | `false` | Whether the RFV is structured or not. Defaults to False.  
`requested_date` | _date_ | `false` | The desired follow up date.  
`note_type_id` | _UUID (str)_ | `false` | The desired type of appointment. See [NoteType](/sdk/data-note/#notetype).  
`coding` | _Coding_ or _UUID (str)_ | `true` if structured=True | The coding for the structured RFV. Either a full Coding object (with `code`, `system`, `display`) or a UUID string referencing a verified coding record. If a Coding is provided, it is validated against existing [ReasonForVisitSettingCoding](/sdk/data-reason-for-visit/#reasonforvisitsettingcoding) records  
`comment` | _string_ | `false` | Additional commentary on the RFV.  
**Example** :
    ```python
    from canvas_sdk.commands import FollowUpCommand
    from datetime import date
    structured = FollowUpCommand(
      note_uuid='8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47',
      structured=True,
      requested_date=date(2025, 3, 2),
      note_type_id="d1e2f3a4-b5c6-4d7e-8f9a-0b1c2d3e4f5a",
      coding={'code': '49727002', 'system': 'http://snomed.info/sct', 'display': 'Cough'},
      comment='also wants to discuss treatment options'
    )
    # Example with a UUID string referencing a Coding record
    structured2 = FollowUpCommand(
      note_uuid='8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47',
      structured=True,
      requested_date=date(2025, 3, 2),
      note_type_id="d1e2f3a4-b5c6-4d7e-8f9a-0b1c2d3e4f5a",
      coding="e2b1e1e3-3f52-4a0a-bb3a-123456789abc",  # Must correspond to an existing coding record
      comment="Discuss treatment options"
    )
    unstructured = FollowUpCommand(
      note_uuid='8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47',
      requested_date=date(2025, 3, 2),
      note_type_id="d1e2f3a4-b5c6-4d7e-8f9a-0b1c2d3e4f5a",
      comment='also wants to discuss treatment options'
    )
    ```
* * *
###  Goal 
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`goal_statement` | _string_ | `true` | Description of the goal.  
`start_date` | _datetime_ | `false` | The date the goal begins.  
`due_date` | _datetime_ | `false` | The date the goal is due.  
`achievement_status` | _AchievementStatus enum_ | `false` | The current achievement status of the goal.  
`priority` | _Priority enum_ | `false` | The priority of the goal.  
`progress` | _string_ | `false` | A narrative about the patient's progress toward the goal.  
`AchievementStatus` | Value | Description  
---|---|---  
`IN_PROGRESS` | `"in-progress"` | The goal is being pursued.  
`IMPROVING` | `"improving"` | Progress toward the goal is improving.  
`WORSENING` | `"worsening"` | Progress toward the goal is worsening.  
`NO_CHANGE` | `"no-change"` | No change in progress toward the goal.  
`ACHIEVED` | `"achieved"` | The goal has been achieved.  
`SUSTAINING` | `"sustaining"` | The achieved goal is being sustained.  
`NOT_ACHIEVED` | `"not-achieved"` | The goal was not achieved.  
`NO_PROGRESS` | `"no-progress"` | No progress has been made toward the goal.  
`NOT_ATTAINABLE` | `"not-attainable"` | The goal is not attainable.  
`Priority` | Value | Description  
---|---|---  
`HIGH` | `"high-priority"` | High priority.  
`MEDIUM` | `"medium-priority"` | Medium priority.  
`LOW` | `"low-priority"` | Low priority.  
**Example** :
    ```python
    from canvas_sdk.commands import GoalCommand
    from datetime import datetime
    goal = GoalCommand(
        note_uuid='8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47',
        goal_statement='Eat more healthy vegetables.',
        start_date=datetime(2024, 1, 1),
        due_date=datetime(2024, 12, 31),
        achievement_status=GoalCommand.AchievementStatus.IN_PROGRESS,
        priority=GoalCommand.Priority.HIGH,
        progress='patient is frequenting local farmers market to find healthy options'
    )
    ```
* * *
###  HistoryOfPresentIllness 
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`narrative` | _string_ | `true` | The narrative of the patient's history of present illness.  
**Example** :
    ```python
    from canvas_sdk.commands import HistoryOfPresentIllnessCommand
    hpi = HistoryOfPresentIllnessCommand(
            note_uuid='8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47',
            narrative='presents with chronic back pain and headaches'
        )
    ```
* * *
###  ImagingOrder 
**Command-specific parameters** :
Name | Type | Required to delegate / sign | Description  
---|---|---|---  
`image_code` | _string_ | `true` | Code identifier of the imaging order. Search with the [imaging-codes endpoint](/sdk/utils/#searching-for-imaging-codes).  
`diagnosis_codes` | _list[string]_ | `true` | ICD-10 Diagnosis codes justifying the imaging order. Search with the [ICD-10 condition endpoint](/sdk/utils/#get-icdcondition--icd-10-conditions).  
`priority` | _Priority enum_ | `false` | Priority of the imaging order. Must be one of `ImagingOrderCommand.Priority`.  
`additional_details` | _string_ | `false` | Additional details or instructions related to the imaging order (max length: 1024 characters).  
`service_provider` | _ServiceProvider_ | `true` | Service provider of the imaging order. Search with the [contacts endpoint](/sdk/utils/#searching-for-contacts-and-service-providers).  
`comment` | _string_ | `false` | Additional comments (max length: 1024 characters).  
`ordering_provider_key` | _string_ | `true` | The [Staff](/sdk/data-staff/#staff) `id` of the provider ordering the imaging.  
`linked_items_urns` | _list[string]_ | `false` | List of URNs for items linked to the imaging order command.  
**Command-specific actions** :
Action Name | Available When | Description  
---|---|---  
`delegate_action` | command is staged | Delegates the order by creating a task.  
`sign_action` | command is staged | Signs the order, transitioning it from staged to committed state.  
`print_specialist` | command is committed | Prints the order using a specialist-focused template.  
`print_patient` | command is committed | Prints the order using a patient-friendly template.  
`fax` | command is committed | Transmits the order electronically via fax.  
**Enums and Types** :
**`Priority`**
Priority | Value | Description  
---|---|---  
`ROUTINE` | `"Routine"` | A routine order.  
`URGENT` | `"Urgent"` | An urgent order.  
`STAT` | `"STAT"` | A STAT (immediate) order.  
**Example** :
    ```python
    from canvas_sdk.commands import ImagingOrderCommand
    from canvas_sdk.commands.constants import ServiceProvider
    imaging_order = ImagingOrderCommand(
        note_uuid="8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47",
        image_code="G0204",
        diagnosis_codes=["E119"],
        priority=ImagingOrderCommand.Priority.ROUTINE,
        comment="this is a comment",
        additional_details="more details",
        ordering_provider_key="b8a7c6d5-4e3f-4a2b-9c1d-0e8f7a6b5c4d",
        service_provider=ServiceProvider(
          first_name="Clinic",
          last_name="Imaging",
          practice_name="Clinic Imaging",
          specialty="radiology",
          business_address="Street Address",
          business_phone="1234569874",
          business_fax="1234569874"
     ),
    )
    ```
* * *
###  ImagingReview 
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`report_ids` | _list[string]_ | `true` | List of [ImagingReport](/sdk/data-imaging/#imagingreport) IDs to review. Must be reports on that patient's chart.  
`message_to_patient` | _string_ | `false` | Message to communicate findings to the patient.  
`communication_method` | _ReportReviewCommunicationMethod enum_ | `false` | Method for patient communication. Must be one of `ReportReviewCommunicationMethod`.  
`linked_items_urns` | _list[string]_ | `false` | List of URNs for items linked to the review.  
`comment` | _string_ | `false` | Internal comment about the review.  
**Example** :
    ```python
    from canvas_sdk.commands import ImagingReviewCommand
    from canvas_sdk.commands.commands.review import ReportReviewCommunicationMethod
    from canvas_sdk.v1.data import ImagingReport, Patient
    patient = Patient.objects.get(id="patient-id")
    # Get imaging reports to review
    imaging_reports = ImagingReport.objects.filter(patient=patient, review__isnull=True, review_mode='RR')
    report_ids = [str(report.id) for report in imaging_reports]
    imaging_review = ImagingReviewCommand(
        note_uuid="a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d",
        report_ids=report_ids,
        message_to_patient="Your imaging results show no abnormalities.",
        communication_method=ReportReviewCommunicationMethod.DELEGATED_CALL_CAN_LEAVE_MESSAGE,
        comment="All clear, no follow-up needed."
    )
    ```
* * *
###  ImmunizationStatement 
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`cpt_code` | _string_ or _Coding_ | `false`* | The CPT code for the immunization procedure. Used with CVX code to search against ontologies server for validation. Search with the [immunization endpoint](/sdk/utils/#get-cptimmunization--search-immunizations).  
`cvx_code` | _string_ or _Coding_ | `false`* | The CVX code for the vaccine administered. Used with CPT code to search against ontologies server for validation. Search with the [immunization endpoint](/sdk/utils/#get-cptimmunization--search-immunizations).  
`unstructured` | _Coding_ | `false`* | Free-text immunization description.  
`approximate_date` | _date_ | `false` | The approximate date when the immunization was administered.  
`comments` | _string_ | `false` | Additional comments about the immunization (max 255 characters).  
*Must provide either both `cpt_code` and `cvx_code` together, or `unstructured` alone (cannot mix structured and unstructured).
**Coding Support** :
The `cpt_code` and `cvx_code` parameters accept either:
  - **String** : Looks up the code in the respective system (CPT or CVX)
  - **Coding object** : Allows structured coding 
    - `cpt_code` must use system: `CPT`
    - `cvx_code` must use system: `CVX`
    - Required fields: `system`, `code`
    - Optional field: `display`
The `unstructured` parameter:
  - **Coding object** : For free-text immunizations 
    - Required system: `UNSTRUCTURED`
    - Required fields: `system`, `code`
    - Optional field: `display`
**Examples** :
    ```python
    from canvas_sdk.commands.commands.immunization_statement import ImmunizationStatementCommand
    from canvas_sdk.commands.constants import CodeSystems, Coding
    from datetime import date
    immunization_statement = ImmunizationStatementCommand(
        cpt_code="90724",
        cvx_code="88",
        approximate_date=date(2024, 1, 15),
        comments="Patient received influenza vaccine"
    )
    # Using Coding objects for structured codes
    immunization_statement_coded = ImmunizationStatementCommand(
        cpt_code=Coding(
            system=CodeSystems.CPT,
            code="90724"
        ),
        cvx_code=Coding(
            system=CodeSystems.CVX,
            code="88"
        ),
        approximate_date=date(2024, 1, 15),
        comments="Patient received influenza vaccine"
    )
    # Using unstructured (free text immunization)
    immunization_statement_unstructured = ImmunizationStatementCommand(
        unstructured=Coding(
            system=CodeSystems.UNSTRUCTURED,
            code="COVID-19 booster at pharmacy"
        ),
        approximate_date=date(2024, 1, 15)
    )
    ```
* * *
###  Immunize 
Records a vaccine **administered** during the visit, including the lot it came from.
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`vaccine_id` | _UUID_ | `true` | The `id` of a [Vaccine](/sdk/data-vaccine/#vaccine) in this instance's catalog. Must be active.  
`lot_id` | _UUID_ | `false`* | The `id` of a [VaccineLot](/sdk/data-vaccine/#vaccinelot) with doses on hand.  
`lot_number` | _string_ | `false`* | A lot number this instance does not stock, recorded as free text (max 20 characters).  
`manufacturer` | _string_ | `false` | The vaccine's manufacturer (max 100 characters).  
`expiration_date` | _date_ | `false` | The lot's expiration date.  
`sig` | _string_ | `false` | Directions, as free text - for example `"0.5 mL IM, left deltoid"` (max 75 characters).  
`consent_given` | _boolean_ | `true` | Whether the patient consented after reviewing the Vaccine Information Statement. Must be `true` to commit.  
`given_by_id` | _string_ | `true` | The `id` of the [Staff](/sdk/data-staff/#staff) member who administered the vaccine. Must be active.  
*`lot_id` and `lot_number` are mutually exclusive; supplying both raises an error. Either may be omitted.
**Choosing a vaccine and lot** :
Both are instance-specific data, so look them up rather than hard-coding identifiers. A vaccine is only selectable on a note if it is active and carries an active CPT charge. See [Vaccine](/sdk/data-vaccine/) for the query.
**Manufacturer and expiration** :
When you supply a `lot_id` and leave `manufacturer` or `expiration_date` unset, the command fills them in from the lot. Anything you set explicitly is used as-is - including an explicit `None`, which is treated as a deliberate choice to leave the field empty rather than as an omission.
A `lot_number` is free text with no inventory record behind it, so nothing is derived from it; set `manufacturer` and `expiration_date` yourself if you want them recorded.
**Example** :
    ```python
    from datetime import date
    from canvas_sdk.commands.commands.immunize import ImmunizeCommand
    from canvas_sdk.v1.data import Vaccine, VaccineLot
    vaccine = Vaccine.objects.filter(active=True, cvx_code="135").first()
    lot = VaccineLot.objects.filter(vaccine__id=vaccine.id, on_hand_inventory__gt=0).first()
    # manufacturer and expiration_date are taken from the lot
    immunize = ImmunizeCommand(
        note_uuid="8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47",
        vaccine_id=vaccine.id,
        lot_id=lot.id,
        sig="0.5 mL IM, left deltoid",
        consent_given=True,
        given_by_id="b8a7c6d5-4e3f-4a2b-9c1d-0e8f7a6b5c4d",
    )
    # A lot the instance does not stock: supply the details yourself
    immunize_unstocked = ImmunizeCommand(
        note_uuid="8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47",
        vaccine_id=vaccine.id,
        lot_number="ABC-12345",
        manufacturer="Acme Vaccines",
        expiration_date=date(2028, 1, 31),
        sig="0.5 mL IM, left deltoid",
        consent_given=True,
        given_by_id="b8a7c6d5-4e3f-4a2b-9c1d-0e8f7a6b5c4d",
    )
    ```
* * *
###  Instruct 
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`coding` | **Coding** | `true` | The SNOMED code or UNSTRUCTURED code that represents the instruction. Search SNOMED with the [instruction endpoint](/sdk/utils/#get-snomedinstruction--instructions).  
`comment` | _string_ | `false` | Additional comments related to the instruction.  
**Example** :
    ```python
    from canvas_sdk.commands import InstructCommand
    from canvas_sdk.commands.constants import CodeSystems, Coding
    # SNOMED code
    InstructCommand(
        note_uuid='8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47',
        coding=Coding(system=CodeSystems.SNOMED, code="65921008"),
        comment="To address mild dehydration symptoms"
    )
    # UNSTRUCTURED code
    InstructCommand(
        note_uuid='8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47',
        coding=Coding(system=CodeSystems.UNSTRUCTURED, code="Physical medicine neuromuscular training"),
    )
    ```
* * *
###  LabOrder 
The `LabOrderCommand` is used to initiate a lab order through the Canvas system. This command requires detailed information about the lab partner, the tests being ordered, and the provider placing the order. Built-in validations ensure that:
  - The specified lab partner exists (whether provided by name or ID).
  - The ordered tests are available for the chosen lab partner.
**Electronic ordering:** LabOrder commands support the `send()` method for electronic ordering of signed orders directly to lab partners. However, electronic ordering has additional requirements:
  - Only lab partners with electronic ordering enabled support the `send()` method.
  - The command must be committed/signed before it can be sent electronically.
  - The patient must have an address and phone number on file.
  - The ordering provider must have an NPI.
**Command-specific parameters** :
Name | Type | Required to send | Description  
---|---|---|---  
`lab_partner` | _string_ | `true` | The [lab partner](/sdk/data-lab-partner-and-test/#labpartner) processing the order. Accepts either the lab partner's name or its unique identifier (ID).  
`tests_order_codes` | _list[string]_ | `true` | A list of codes or IDs for the [tests](/sdk/data-lab-partner-and-test/#labpartnertest-attributes) being ordered. The system verifies that each provided value corresponds to an available test for the specified lab partner.  
`ordering_provider_key` | _string_ | `false` | The [Staff](/sdk/data-staff/#staff) `id` of the provider ordering the tests.  
`diagnosis_codes` | _list[string]_ | `false` | ICD-10 Diagnosis codes justifying the lab order. Search with the [ICD-10 condition endpoint](/sdk/utils/#get-icdcondition--icd-10-conditions).  
`fasting_required` | _boolean_ | `false` | Indicates if fasting is required for the tests.  
`comment` | _string_ | `false` | Additional comments related to the lab order (max length: 128 characters).  
**Command-specific actions** :
Action Name | Available When | Description  
---|---|---  
`sign_send_action` | command is staged | Signs and immediately sends the order electronically to the lab partner.  
`send_action` | command is staged | Sends the order electronically to the chosen lab partner.  
`sign_action` | command is staged | Signs the order, transitioning it from staged to committed state.  
`print_requisition_form` | command is committed | Prints the order using a requisition-focused template for lab submission.  
`print_specimen_label` | command is committed | Prints the template using a specimen-focused template.  
`fax_requisition_form` | command is committed | Transmits the order electronically via fax.  
**ABN Workflow Actions**
When the ABN (Advance Beneficiary Notice) workflow is enabled, additional actions become available:
Action Name | Available When | Description  
---|---|---  
`send_abn_signed` | command is staged | Sends the order electronically after ABN requirements are met.  
`make_changes` | command is staged | Allows modifications to complete ABN requirements before sending.  
####  Validations 
  - **Lab Partner Validation:** The system checks that the provided `lab_partner` (by name or ID) exists in the system. If no matching lab partner is found, a validation error is raised.
  - **Tests Order Codes Validation:** Each test code or ID in `tests_order_codes` is verified against the tests available for the specified lab partner. If one or more tests cannot be found, the error will indicate which codes or IDs are missing.
**Example** :
    ```python
    from canvas_sdk.commands import LabOrderCommand
    from canvas_sdk.v1.data.lab import LabPartner, LabPartnerTest
    partner = LabPartner.objects.first()
    tests = [test.order_code for test in LabPartnerTest.objects.filter(lab_partner=partner)]
    LabOrderCommand(
      lab_partner=str(partner.id),
      tests_order_codes=tests,
      ordering_provider_key="b8a7c6d5-4e3f-4a2b-9c1d-0e8f7a6b5c4d",
      diagnosis_codes=["E119"],
      fasting_required=True,
      comment="Patient should fast for 8 hours before the test."
    )
    ```
* * *
###  LabReview 
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`report_ids` | _list[string]_ | `true` | List of [LabReport](/sdk/data-labs/#labreport) IDs to review. Must be reports on that patient's chart.  
`message_to_patient` | _string_ | `false` | Message to communicate findings to the patient.  
`communication_method` | _ReportReviewCommunicationMethod enum_ | `false` | Method for patient communication. Must be one of `ReportReviewCommunicationMethod`.  
`linked_items_urns` | _list[string]_ | `false` | List of URNs for items linked to the review.  
`comment` | _string_ | `false` | Internal comment about the review.  
**Example** :
    ```python
    from canvas_sdk.commands import LabReviewCommand
    from canvas_sdk.commands.commands.review import ReportReviewCommunicationMethod
    from canvas_sdk.v1.data import LabReport, Patient
    patient = Patient.objects.get(id="patient-id")
    # Get lab reports to review
    lab_reports = LabReport.objects.filter(patient=patient, review__isnull=True, review_mode='RR')
    report_ids = [str(report.id) for report in lab_reports]
    lab_review = LabReviewCommand(
        note_uuid="a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d",
        report_ids=report_ids,
        message_to_patient="Your lab results are within normal range.",
        communication_method=ReportReviewCommunicationMethod.DELEGATED_CALL_CAN_LEAVE_MESSAGE,
        comment="All values normal, no follow-up needed."
    )
    ```
* * *
###  MedicalHistory 
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`past_medical_history` | _string_ | `true` | An ICD-10 code or description of the past medical condition. ICD-10 codes are strongly preferred (see note below). Search with the [ICD-10 condition endpoint](/sdk/utils/#get-icdcondition--icd-10-conditions).  
`approximate_start_date` | _date_ | `false` | Approximate start date of the condition.  
`approximate_end_date` | _date_ | `false` | Approximate end date of the condition.  
`show_on_condition_list` | _boolean_ | `false` | Whether the condition should appear on the condition list.  
`comments` | _string_ | `false` | Additional comments (max length: 1000 characters).  
**Important: Use ICD-10 codes for accurate matching.** The `past_medical_history` field searches for matching conditions and selects the first result. When a text description is provided, similar conditions may match first. To guarantee the correct condition, pass the ICD-10 code directly (e.g., `"I1010"`).
**Example** :
    ```python
    from canvas_sdk.commands import MedicalHistoryCommand
    from datetime import date
    # Preferred: use the ICD-10 code for exact matching
    MedicalHistoryCommand(
        past_medical_history="I1010",  # Resistant Hypertension
        approximate_start_date=date(2015, 1, 1),
        show_on_condition_list=True,
        comments="Controlled with medication."
    )
    # Also works but may match a different condition if the description is ambiguous
    MedicalHistoryCommand(
        past_medical_history="Resistant Hypertension",
        approximate_start_date=date(2015, 1, 1),
        show_on_condition_list=True,
        comments="Controlled with medication."
    )
    ```
* * *
###  MedicationStatement 
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`fdb_code` | _string_ or _Coding_ | `true` | The [FDB code](/sdk/utils/#fdb_code) of the medication  
`sig` | _string_ | `false` | Administration details of the medication (max length: 1000 characters).  
**Coding Support** :
The `fdb_code` parameter accepts either:
  - **String (FDB code)** : Looks up the medication in the FDB system
  - **Coding object** : Allows structured or unstructured coding 
    - Supported systems: `FDB`, `UNSTRUCTURED`
    - Required fields: `system`, `code`
    - Optional field: `display`
**Example** :
    ```python
    from canvas_sdk.commands import MedicationStatementCommand
    from canvas_sdk.commands.constants import CodeSystems, Coding
    # Using an FDB code string (recommended for FDB medications)
    medication_statement = MedicationStatementCommand(
        note_uuid='8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47',
        fdb_code='198698',
        sig='two pills taken orally'
    )
    # Using an FDB Coding object
    medication_statement_fdb = MedicationStatementCommand(
        note_uuid='8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47',
        fdb_code=Coding(
            system=CodeSystems.FDB,
            code='198698',
            display='aspirin 81 mg oral tablet'
        ),
        sig='two pills taken orally'
    )
    # Using unstructured (free text medication)
    medication_statement_unstructured = MedicationStatementCommand(
        note_uuid='8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47',
        fdb_code=Coding(
            system=CodeSystems.UNSTRUCTURED,
            code='Herbal supplement for joint health'
        )
    )
    ```
* * *
###  SurgicalHistory 
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`past_surgical_history` | _string_ or _Coding_ | `true` | A description of the past surgical procedure. Search with the [procedures endpoint](/sdk/utils/#get-snomedprocedures--surgical-history-procedures).  
`approximate_date` | _date_ | `false` | Approximate date of the surgery.  
`comment` | _string_ | `false` | Additional comments (max length: 1000 characters).  
**Coding Support** :
The `past_surgical_history` parameter accepts either:
  - **String** : Searches for matching surgical procedures and selects the first result.
  - **Coding object** : Allows structured or unstructured coding 
    - Supported systems: `SNOMED`, `UNSTRUCTURED`
    - Required fields: `system`, `code`
    - Optional field: `display`
**Example** :
    ```python
    from canvas_sdk.commands import PastSurgicalHistoryCommand
    from canvas_sdk.commands.constants import CodeSystems, Coding
    from datetime import date
    # Using a string (searches and takes the first result)
    PastSurgicalHistoryCommand(
        past_surgical_history="Appendectomy",
        approximate_date=date(2008, 6, 15),
        comment="No complications reported."
    )
    # Using a SNOMED code
    surgical_history_snomed = PastSurgicalHistoryCommand(
        past_surgical_history=Coding(
            system=CodeSystems.SNOMED,
            code="80146002",
            display="Appendectomy"
        ),
        approximate_date=date(2008, 6, 15),
        comment="No complications reported."
    )
    # Using unstructured (free text)
    surgical_history_unstructured = PastSurgicalHistoryCommand(
        past_surgical_history=Coding(
            system=CodeSystems.UNSTRUCTURED,
            code="Minor outpatient procedure on left knee"
        ),
        approximate_date=date(2020, 3, 10)
    )
    ```
* * *
###  Perform 
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`cpt_code` | _string_ or _Coding_ | `true` | The CPT code of the procedure or action performed. Look it up in the [Charge Description Master](/sdk/data-charge-description-master/#chargedescriptionmaster).  
`notes` | _string_ | `false` | Additional notes related to the performed procedure.  
**Coding Support** :
The `cpt_code` parameter accepts either:
  - **String** : Searches for matching procedures
  - **Coding object** : Allows structured or unstructured coding 
    - Supported systems: `CPT`, `UNSTRUCTURED`
    - Required fields: `system`, `code`
    - Optional field: `display`
**Example** :
    ```python
    from canvas_sdk.commands import PerformCommand
    from canvas_sdk.commands.constants import CodeSystems, Coding
    # Using a string (searches for matching procedures)
    PerformCommand(
        cpt_code="99213",
        notes="Patient presented with a common cold."
    )
    # Using a CPT code
    perform_cpt = PerformCommand(
        cpt_code=Coding(
            system=CodeSystems.CPT,
            code="99213",
            display="Office visit, established patient"
        ),
        notes="Annual wellness visit"
    )
    # Using unstructured (free text)
    perform_unstructured = PerformCommand(
        cpt_code=Coding(
            system=CodeSystems.UNSTRUCTURED,
            code="Custom procedure performed"
        ),
        notes="Non-standard procedure documentation"
    )
    ```
* * *
###  Plan 
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`narrative` | _string_ | `true` | The narrative of the patient's plan.  
**Example** :
    ```python
    from canvas_sdk.commands import PlanCommand
    plan = PlanCommand(
        note_uuid='8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47',
        narrative='will return in 2 weeks to check on pain management'
    )
    ```
* * *
###  POCLabTest 
The `POCLabTestCommand` is used to document the results of a Point-of-Care (POC) lab test performed in the clinic — distinct from `LabOrder` (which sends tests to an external lab partner) and `LabReview` (which reviews returned results). The command captures the template used, the indications, individual measured values, and a free-text remarks field.
Built-in validations ensure that:
  - The provided `template` UUID resolves to an active POC [`LabReportTemplate`](/sdk/data-lab-report-template/#labreporttemplate).
  - Each `test_values` entry's `label` matches one of the template's [field labels](/sdk/data-lab-report-template/#labreporttemplatefield) (case-insensitive).
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`template` | _UUID | string_ | `true` | The UUID of the active POC [`LabReportTemplate`](/sdk/data-lab-report-template/#labreporttemplate). Accepts UUID instances or UUID-formatted strings.  
`indications` | _list[string]_ | `false` | ICD-10 diagnosis codes justifying the test. Search with the [ICD-10 condition endpoint](/sdk/utils/#get-icdcondition--icd-10-conditions).  
`test_values` | _list[TestValue]_ | `false` | The measured values, each tagged with its template-field label. See `TestValue` below.  
`remarks` | _string (≤512)_ | `false` | Free-text comments from the clinician.  
**Enums and Types** :
**`TestValue`**
A dataclass representing a single measured value within a POC lab test result.
Attribute | Type | Description  
---|---|---  
`label` | _string_ | The template field's label (must match a field on the template).  
`value` | _string_ | The measured value (as a string).  
`TestValue.to_dict()` returns the `{"label": ..., "value": ...}` dict shape consumed by the runtime.
**Helper methods** :
  - `set_test_value(label, value)` — Adds or replaces a test value by label. If a `TestValue` with the same `label` already exists on the command, it is replaced (so calling `set_test_value` twice with the same label leaves a single entry).
####  Validations 
  - **Template Validation:** The `template` UUID must resolve to a [`LabReportTemplate`](/sdk/data-lab-report-template/#labreporttemplate) that is `active=True` and `poc=True`. Templates from external lab partners or inactive templates are rejected.
  - **Test Values Validation:** Each `TestValue.label` must match (case-insensitive) the `label` of one of the resolved template's [fields](/sdk/data-lab-report-template/#labreporttemplatefield). Unknown labels cause a validation error.
The valid labels are the `label` of each [`LabReportTemplateField`](/sdk/data-lab-report-template/#labreporttemplatefield) on the template's [`fields`](/sdk/data-lab-report-template/#labreporttemplate) relation:
    ```python
    from canvas_sdk.v1.data import LabReportTemplate
    template = LabReportTemplate.objects.active().point_of_care().first()
    valid_labels = [field.label for field in template.fields.all()]
    ```
**Example** :
    ```python
    from canvas_sdk.commands import POCLabTestCommand
    from canvas_sdk.commands.commands.poc_lab_test import TestValue
    from canvas_sdk.v1.data import LabReportTemplate
    template = LabReportTemplate.objects.active().point_of_care().first()
    command = POCLabTestCommand(
        note_uuid="8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47",
        template=template.id,
        indications=["E11.9"],
        test_values=[
            TestValue(label="pH", value="6.5"),
            TestValue(label="Glucose", value="120"),
        ],
        remarks="Sample collected mid-stream",
    )
    # Or via the helper (overwrites by label):
    command.set_test_value("pH", "6.8")
    ```
* * *
###  Prescribe 
**Electronic prescribing:** Prescribe commands support the `send()` method for electronic transmission of signed prescriptions. However, electronic prescribing has additional validations:
  - A pharmacy must be specified on the command before it can be sent.
  - The command must be committed/signed before it can be sent electronically.
  - The prescriber must have an SPI (Surescripts Prescriber Identifier) number on file, or the send is restricted with `eRx unavailable, prescriber missing SPI number`. SPI is a send requirement only: a prescriber without one can still review and sign the prescription.
  - For a controlled substance, the prescriber must be enrolled in EPCS, or the send is restricted with `eRx unavailable, prescriber not enrolled in EPCS`.
  - For a controlled substance (a medication with a DEA schedule), the patient's [sex at birth](/sdk/data-patient/#sexatbirth) must be male or female, or the send is restricted with `eRx unavailable, patient sex at birth must be male or female`.
These validations apply to Refill and AdjustPrescription as well, and in the Canvas UI as well as through the SDK — in the UI a restricted prescription offers no send action at all.
**Overriding the prescriber address:** By default, the prescriber address transmitted on the prescription is derived from the prescriber's primary practice location. For workflows where a provider works across multiple offices — for example white bagging, where the medication ships to the office where the patient is being seen — pass a `practice_location_override` to `send()` to use a specific practice location's address instead:
    ```python
    from canvas_sdk.commands import PrescribeCommand
    def compute():
        existing_prescribe = PrescribeCommand(command_uuid='e32b85d9-ccb7-4e4f-a0e5-8783ed2d9528')
        return [existing_prescribe.send(practice_location_override='a1b2c3d4-e5f6-7890-abcd-ef1234567890')]
    ```
  - `practice_location_override` is the `id` of a [PracticeLocation](/sdk/data-practicelocation/#practicelocation). When set, that location's business name, phone, fax, and street address replace the prescriber's default on the outgoing prescription.
  - If the id does not correspond to an existing practice location, the send raises an error rather than falling back to the default address.
  - The override applies only to `send()`-initiated (plugin-driven) prescriptions. It does not affect prescriptions a clinician sends from the charting UI.
**Command-specific parameters** :
Name | Type | Required to review / send | Description  
---|---|---|---  
`fdb_code` | _string_ | `false`* | The [FDB code](/sdk/utils/#fdb_code) of the medication.  
`compound_medication_id` | _string_ | `false`* | The id of an existing [CompoundMedication](/sdk/data-compound-medication/#compoundmedication) to prescribe.  
`compound_medication_data` | `CompoundMedicationData` | `false`* | Data for creating a new compound medication inline.  
`icd10_codes` | _list[string]_ | `false` | List of ICD-10 codes (maximum 2) associated with the prescription. Must be [Conditions](/sdk/data-condition/#condition) on the patient's active problem list.  
`sig` | _string_ | `true` | Administration instructions/details of the medication. Up to 1000 characters — see Limits.  
`days_supply` | _integer_ | `false` | Number of days the prescription is intended to cover.  
`quantity_to_dispense` | _Decimal | float | integer_ | `true` | The amount of medication to dispense. Must be greater than zero — see Limits.  
`type_to_dispense` | _ClinicalQuantity_ | `true`** | Information about the form or unit of the medication to dispense. Get the available quantities from the [medication search](/sdk/utils/#searching-for-medications)'s `clinical_quantities`.  
`refills` | _integer_ | `true` | Number of refills allowed for the prescription. From 0 to 99 — see Limits.  
`substitutions` | _Substitutions enum_ | `true` | Specifies whether substitutions (e.g., generic drugs) are allowed.  
`pharmacy` | _string_ | `false` | The NCPDP ID of the pharmacy where the prescription should be sent. [Look it up via the pharmacy search](/sdk/utils/#searching-for-pharmacies).  
`prescriber_id` | _string_ | `true` | The [Staff](/sdk/data-staff/#staff) id of the prescriber.  
`supervising_provider_id` | _string_ | `false` | The [Staff](/sdk/data-staff/#staff) id of the supervising provider of the prescriber.  
`note_to_pharmacist` | _string_ | `false` | Additional notes or instructions for the pharmacist. Up to 210 characters — see Limits.  
*Must provide exactly one of: fdb_code, compound_medication_id, or compound_medication_data
**ClinicalQuantity is only required when `fdb_code` is provided. It is optional for compound medications.
**Command-specific actions** :
Action Name | Available When | Description  
---|---|---  
`sign_send_action` | command is in review | Signs and immediately sends the prescription electronically.  
`sign_action` | command is in review | Signs the prescription, transitioning it from staged to committed state.  
`print_action` | command is in review | Prints and commits the command.  
`make_changes` | command is in review | Allow users to revert the command to staged state and make changes.  
`send_action` | command is committed | Sends the prescription electronically.  
**Enums and Types**
Substitutions | Value | Description  
---|---|---  
`ALLOWED` | `"allowed"` | Generic or substitute medications are permitted.  
`NOT_ALLOWED` | `"not_allowed"` | Only the prescribed brand is allowed.  
**CompoundMedicationData** : Data for creating a compound medication inline within a prescription.
Field Name | Type | Description | Required  
---|---|---|---  
`formulation` | _string_ | The compound medication formulation (max 105 characters) | `true`  
`potency_unit_code` | _[PotencyUnit](/sdk/data-compound-medication/#potencyunit) value_ | The unit of measurement for the medication. | `true`  
`controlled_substance` | _[ControlledSubstanceSchedule](/sdk/data-compound-medication/#controlledsubstanceschedule) value_ | The controlled substance schedule (`N` for none). | `true`  
`controlled_substance_ndc` | _string_ | NDC for controlled substances (dashes removed) | `false`*  
`active` | _bool_ | Whether the compound medication is active (default: true) | `false`  
*Required when controlled_substance is not "N" (None)
**Examples**
**_Option 1: Standard Prescription (FDB Code)_**
    ```python
    from canvas_sdk.commands.constants import ClinicalQuantity
    from canvas_sdk.commands import PrescribeCommand
    prescription = PrescribeCommand(
        fdb_code="216092",
        icd10_codes=["R51"],
        sig="Take one tablet daily after meals",
        days_supply=30,
        quantity_to_dispense=30,
        type_to_dispense=ClinicalQuantity(
            representative_ndc="12843016128",
            ncpdp_quantity_qualifier_code="C48542"
        ),
        refills=3,
        substitutions=PrescribeCommand.Substitutions.ALLOWED,
        pharmacy="pharmacy_ncpdp_id",
        prescriber_id="a7c2e9d1-3b4f-4a6c-8e0d-5f1a2b3c4d5e",
        supervising_provider_id='c3d4e5f6-7a8b-4c9d-0e1f-2a3b4c5d6e7f',
        note_to_pharmacist="Please verify patient's insurance before processing."
    )
    ```
**_Option 2: Existing Compound Medication (by ID)_**
Note: `type_to_dispense` should not be provided for compound medications as this field will auto-populate in the command when it is inserted in the note
    ```python
    from canvas_sdk.commands.constants import ClinicalQuantity
    from canvas_sdk.commands import PrescribeCommand
    from canvas_sdk.v1.data.compound_medication import CompoundMedication as CompoundMedicationModel
    # Get an existing compound medication (let's assume it exists in the database)
    compound_med = CompoundMedicationModel.objects.filter(
        active=True,
        formulation="Testosterone 200mg/mL in Grapeseed Oil"
    ).first()
    prescription = PrescribeCommand(
        compound_medication_id=str(compound_med.id),
        icd10_codes=["R51"],
        sig="Take one tablet daily after meals",
        days_supply=30,
        quantity_to_dispense=30,
        refills=3,
        substitutions=PrescribeCommand.Substitutions.ALLOWED,
        pharmacy="pharmacy_ncpdp_id",
        prescriber_id="a7c2e9d1-3b4f-4a6c-8e0d-5f1a2b3c4d5e",
        supervising_provider_id='c3d4e5f6-7a8b-4c9d-0e1f-2a3b4c5d6e7f',
        note_to_pharmacist="Please verify patient's insurance before processing."
    )
    ```
**_Option 3: Create New Compound Medication Inline_**
    ```python
    from canvas_sdk.commands.constants import ClinicalQuantity
    from canvas_sdk.commands.commands.prescribe import PrescribeCommand, CompoundMedicationData
    from canvas_sdk.v1.data.compound_medication import CompoundMedication
    compound_medication_data = CompoundMedicationData(
        formulation="Testosterone 200mg/mL in Grapeseed Oil",
        potency_unit_code=CompoundMedication.PotencyUnits.GRAM,
        controlled_substance=CompoundMedication.ControlledSubstanceOptions.SCHEDULE_III,
        controlled_substance_ndc="12345678901",
        active=True,
    )
    prescription = PrescribeCommand(
        compound_medication_data=compound_medication_data,
        icd10_codes=["M79.3"],
        sig="Apply thin layer to affected area twice daily",
        days_supply=30,
        quantity_to_dispense=30,
        refills=3,
        substitutions=PrescribeCommand.Substitutions.ALLOWED,
        pharmacy="pharmacy_ncpdp_id",
        prescriber_id="a7c2e9d1-3b4f-4a6c-8e0d-5f1a2b3c4d5e",
        supervising_provider_id='c3d4e5f6-7a8b-4c9d-0e1f-2a3b4c5d6e7f',
        note_to_pharmacist="Please verify patient's insurance before processing."
    )
    ```
**Validation Notes**
  - Medication Type Validation: Exactly one of fdb_code, compound_medication_id, or compound_medication_data must be provided
  - Compound Medication ID: When using compound_medication_id, the system validates that the compound medication exists
  - Compound Medication Data: When using compound_medication_data: 
    - All required fields in the dataclass must be provided
    - If controlled substance is not "N" (None), then controlled_substance_ndc is required
    - The formulation is limited to 105 characters
    - Any dashes in the NDC are automatically removed
    - Before creating a new compound medication, the system checks if a compound with the same formulation and potency unit code already exists. If it does, it reuses the existing compound medication instead of creating a new one.
  - Potency Unit and Controlled Substance Values: Must use valid enum values from PotencyUnit and ControlledSubstanceSchedule
**Limits**
A prescription has to fit what can be transmitted to the pharmacy, so four fields are bounded. These apply to Refill and AdjustPrescription as well, which share the fields.
Field | Limit  
---|---  
`sig` | 1000 characters  
`note_to_pharmacist` | 210 characters  
`refills` | 0 to 99  
`quantity_to_dispense` | greater than 0  
All four are checked when the command is turned into an effect, not when the field is set. Building a command up field by field therefore never fails part-way through, and `originate()` or `edit()` reports every value that is out of bounds at once:
    ```python
    from canvas_sdk.commands import PrescribeCommand
    def compute():
        prescribe = PrescribeCommand(note_uuid='c4d1e4b8-6a5f-4b3a-9e2d-7f8a9b0c1d2e')
        # Neither assignment raises.
        prescribe.refills = 100
        prescribe.quantity_to_dispense = 0
        # This raises a validation error naming both values.
        return [prescribe.originate()]
    ```
* * *
###  PhysicalExam 
**Note:** The PhysicalExamCommand is a subclass of the QuestionnaireCommand, so it supports all the questionnaire features. That includes recording responses either with the `answers` parameter or with the `questions` property and `add_response()` — see Recording responses.
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`questionnaire_id` | _string_ | `true` | The id of the [Questionnaire](/sdk/data-questionnaire/#questionnaire) being answered by the patient.  
`answers` | _list ofAnswer_ | `false` | The responses to record, one per question. Defaults to an empty list.  
####  Toggle Questions Feature 
The PhysicalExamCommand and the ReviewOfSystemsCommand both support toggling questions on/off, so practitioners can enable or disable specific questions based on patient relevance. The methods, property, and behavior described here are identical for both commands.
The following methods are available. In each, `question_id` is the [Question](/sdk/data-questionnaire/#question) `dbid` (an integer, accepted as `int` or `str`):
**Methods** :
Method | Parameters | Returns | Description  
---|---|---|---  
`is_question_enabled` | `question_id: str` or `int` | `bool` | Check if a specific question is enabled (not skipped).  
`set_question_enabled` | `question_id: str` or `int, enabled: bool` | `None` | Enable or disable a specific question.  
**Properties** :
Property | Type | Description  
---|---|---  
`question_toggles` | `dict` | All current toggle states, mapping `question_id` → `enabled` — e.g. `{"12": True, "13": False, "14": True}`.  
**Example - Working with Existing Commands** :
A common use case is retrieving existing PhysicalExam commands from a note and modifying their toggle states. Here's how to work with the Canvas SDK data objects:
    ```python
    from canvas_sdk.commands import PhysicalExamCommand
    from canvas_sdk.v1.data import Command, Note
    from logger import log
    # Get existing physical exam commands from a note
    note = Note.objects.get(id="ff287601-fff4-46c4-b21f-04760e88adf1")
    physical_exam_commands = Command.objects.filter(
        note=note,
        schema_key="exam"  # Physical exam commands have schema_key "exam"
    ).all()
    effects = []
    for command in physical_exam_commands:
        # The command.data contains the question responses and skip states
        # Example structure of command.data:
        # {
        #     "questionnaire": {"value": "83d93454-25a9-404d-83a5-e0ed2ec3af00"},
        #     "question-12": "70",  # Body length response
        #     "question-13": None,   # Head circumference (no response)
        #     "skip-12": True,   # Body length is enabled (counterintuitive: skip=True means enabled)
        #     "skip-13": False,  # Head circumference is disabled
        # }
        # Create a PhysicalExamCommand instance from the existing command
        exam = PhysicalExamCommand(command_uuid=str(command.id))
        # The exam.questions property gives you access to all questions with their IDs
        log.info(f"Processing Physical Exam Command: {exam.command_uuid}")
        for question in exam.questions:
            # Each question object has an 'id' property with the question ID
            question_id = question.dbid
            if exam.is_question_enabled(question_id):
                log.info(f"Question {question_id} is enabled")
                # Check if there's a response in the command data
                question_key = f"question-{question_id}"
                if question_key in command.data:
                    response = command.data[question_key]
                    if response:
                        log.info(f"Response: {response}")
        # Example: Enable all questions that have responses, disable those without
        for question in exam.questions:
            question_id = question.dbid
            question_key = f"question-{question_id}"
            # Check if question has a response in command.data
            has_response = question_key in command.data and command.data[question_key]
            if has_response:
                exam.set_question_enabled(question_id, True)
            else:
                # Optionally disable questions without responses
                exam.set_question_enabled(question_id, False)
        effects.append(exam.edit())
    ```
**Example - Creating a New Physical Exam** :
    ```python
    from canvas_sdk.commands import PhysicalExamCommand
    # Create a new physical exam
    exam = PhysicalExamCommand(
      note_uuid='a229456f-c10d-4f85-a04e-e8675d4e56dd',
      questionnaire_id='83d93454-25a9-404d-83a5-e0ed2ec3af00',
    )
    questions = exam.questions  # Retrieve the list of questions
    # Returns: [
    #               Question(
    #                       self.name='question-12',
    #                       self.label='Body length (in)',
    #                       self.type='TXT',
    #                       self.options=[ResponseOption(self.dbid=38, self.name='Body length (in)', self.code='8306-3', self.value='')],
    #                       self.response=None
    #               ),
    #               Question(
    #                       self.name='question-13',
    #                       self.label='Head circumference (cm)',
    #                       self.type='TXT', self.options=[ResponseOption(self.dbid=39, self.name='Head circumference (cm)', self.code='8287-5', self.value='')],
    #                       self.response=None
    #               )
    # Check if a question is enabled
    if exam.is_question_enabled("12"):
      print("Body length question is enabled.")
    # Disable irrelevant questions
    exam.set_question_enabled("13", False)
    # Get all toggle states
    states = exam.question_toggles
    # Returns: {"12": True, "13": False, "14": True, ...}, where keys are question IDs and values are enabled states.
    # Working with existing exam - toggle states are preserved
    existing_exam = PhysicalExamCommand(command_uuid='d4e5f6a7-8b9c-4d0e-1f2a-3b4c5d6e7f80')
    # All previously set toggle states are automatically loaded
    ```
* * *
###  Questionnaire 
####  Overview 
The `QuestionnaireCommand` is used to present a questionnaire to a patient and commit their responses to an interview. It requires the ID of the questionnaire
**Automatic Questionnaire ID Loading** : When instantiating a QuestionnaireCommand with an existing `command_uuid`, the questionnaire_id will be automatically loaded from the database if not explicitly provided. This means you don't need to specify the questionnaire_id when working with existing commands.
In addition to the basic parameters, this command records responses in either of two ways:
  - **The`answers` parameter** — you pass the responses in, one per question, and the command works out how to apply each one. Nothing in your code branches on a question's type. Use this when you already have the question and option ids.
  - **The`questions` property with `add_response()`** — you read the questionnaire's questions off the command and record a response on each question object. The keyword you pass differs by question type, so your code branches on it. Use this when you need to inspect the questions or their options at runtime to decide what to answer.
Both arrive at the same result, and they can be combined. `answers` is applied when the command's effect is built: it replaces whatever was recorded on the questions it names, and leaves a response recorded with `add_response()` on any other question alone. `answers` is not itself carried in the effect.
**Recording responses with`answers`**
The `answers` parameter takes a list of `Answer` objects, one per question. Each names a question and the response it takes; the command looks up the question, dispatches on its type, and resolves an option id to the option itself. A question id that is not in the questionnaire, an option id the question does not offer, or a response the question's type does not allow raises a `ValueError` when the effect is built.
**`Answer` fields**:
Name | Type | Required | Description  
---|---|---|---  
`question_id` | _integer_ | `true` | The [Question](/sdk/data-questionnaire/#question) `dbid`.  
`response` | _string_ , _integer_ , or _list ofSelection_ | `true` | Text for a text question, a number for an integer question, a [ResponseOption](/sdk/data-questionnaire/#responseoption) `dbid` for a radio question, an ISO 8601 `YYYY-MM-DD` string for a date question, or a list of `Selection` objects for a checkbox question.  
A checkbox question is the only kind whose responses carry comments, and each of its selections carries its own — so a comment belongs to a `Selection` rather than to the answer as a whole.
> **Warning:** A date answer given through `answers` must be a string. `response` accepts a string, an integer or a list of `Selection`, so a `datetime.date` is refused. The question's own `add_response(date=...)` is the path that takes a `datetime.date` or a `datetime.datetime`. 
**`Selection` fields**:
Name | Type | Required | Description  
---|---|---|---  
`option_id` | _integer_ | `true` | The [ResponseOption](/sdk/data-questionnaire/#responseoption) `dbid` to tick.  
`comment` | _string_ | `false` | What this selection is qualified with.  
`selected` | _boolean_ | `false` | Defaults to `true`. Set it to `false` to untick the option — one a payload says nothing about keeps the state it already had.  
**Recording responses with`questions` and `add_response()`**
Retrieve the list of questions via the `questions` property and record responses for each question using the question object's `add_response()` method. Each question type enforces its expected response format:
  - **Text questions (TYPE_TEXT):** Accept a keyword argument `text` (a string).
  - **Integer questions (TYPE_INTEGER):** Accept a keyword argument `integer` (a value convertible to an integer; a non-convertible value raises an error).
  - **Radio questions (TYPE_RADIO):** Accept a keyword argument `option` (a `ResponseOption` instance); only one option may be selected.
  - **Checkbox questions (TYPE_CHECKBOX):** Accept a keyword argument `option` (a `ResponseOption` instance) along with an optional boolean `selected` (defaulting to True) and an optional string `comment`. Multiple responses can be recorded.
  - **Date questions (TYPE_DATE):** Accept a keyword argument `date` (a `datetime.date`, a `datetime.datetime` normalized to its date, or an ISO 8601 date string `YYYY-MM-DD`). The value is stored as a normalized `YYYY-MM-DD` string; a string carrying a time component, an unparseable string, or a wrong type raises an error.
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`questionnaire_id` | _string_ | `true` | The id of the [Questionnaire](/sdk/data-questionnaire/#questionnaire) being answered by the patient.  
`answers` | _list ofAnswer_ | `false` | The responses to record, one per question. Defaults to an empty list.  
**Example** — instantiating an empty questionnaire:
    ```python
    from canvas_sdk.commands import QuestionnaireCommand
    questionnaire = QuestionnaireCommand(
        note_uuid='8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47',
        questionnaire_id='c1a2b3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d'
    )
    ```
####  Usage Example 
Below is an example that answers a questionnaire with `answers`. Each `Answer` names a question by its `dbid` and gives the response in the form that question takes, so nothing branches on the question's type:
    ```python
    import uuid
    from canvas_sdk.commands.commands.questionnaire import Answer, QuestionnaireCommand, Selection
    from canvas_sdk.effects import Effect
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.v1.data import Note, Questionnaire
    class MyHandler(BaseHandler):
        def compute(self) -> list[Effect]:
          questionnaire = Questionnaire.objects.filter(name="Exercise").first()
          note = Note.objects.last()
          command = QuestionnaireCommand(
              note_uuid=str(note.id),
              questionnaire_id=str(questionnaire.id),
              command_uuid=str(uuid.uuid4()),
              answers=[
                  # A text question.
                  Answer(question_id=12, response="Thanks for all the fish"),
                  # An integer question.
                  Answer(question_id=13, response=42),
                  # A radio question, answered with the id of one of its options.
                  Answer(question_id=14, response=101),
                  # A date question. Give the date as a string, not a datetime.date.
                  Answer(question_id=15, response="2026-07-14"),
                  # A checkbox question, answered with one Selection per option ticked.
                  Answer(
                      question_id=16,
                      response=[
                          Selection(option_id=201),
                          Selection(option_id=202, comment="Don't panic"),
                      ],
                  ),
              ],
          )
          # Because we're directly setting a command_uuid, we can return both originate and edit.
          return [command.originate(), command.edit()]
    ```
An option id that the question does not offer, or a question id that is not in the questionnaire, raises a `ValueError` rather than recording something the questionnaire does not define.
Below is the same thing written the other way, retrieving the questions and adding responses to them based on their type:
    ```python
    import uuid
    from canvas_sdk.commands.commands.questionnaire import QuestionnaireCommand
    from canvas_sdk.commands.commands.questionnaire.question import ResponseOption
    from canvas_sdk.effects import Effect
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.v1.data import Note, Questionnaire
    class MyHandler(BaseHandler):
        def compute(self) -> list[Effect]:
          q = Questionnaire.objects.filter(name="Exercise").first()
          note = Note.objects.last()
          # Create a QuestionnaireCommand instance.
          command = QuestionnaireCommand(questionnaire_id=str(q.id))
          command.note_uuid = str(note.id)
          command.command_uuid = str(uuid.uuid4())
          # Alternatively you can just retrieve an existing questionnaire command, and only return an `edit` effect.
          # Retrieve the list of questions.
          questions = command.questions
          # Record responses for each question.
          for question in questions:
              if question.type == ResponseOption.TYPE_TEXT:
                  # For text questions, pass a 'text' keyword argument.
                  question.add_response(text=f"Thanks for all the fish")
              elif question.type == ResponseOption.TYPE_INTEGER:
                  # For integer questions, pass an 'integer' keyword argument.
                  question.add_response(integer=42)
              elif question.type == ResponseOption.TYPE_RADIO:
                  # For radio questions, pass an 'option' keyword argument (a ResponseOption instance).
                  first_option = question.options[0]
                  question.add_response(option=first_option)
              elif question.type == ResponseOption.TYPE_CHECKBOX:
                  # For checkbox questions, add responses with option, selected flag, and optionally a comment.
                  first_option = question.options[0]
                  last_option = question.options[-1]
                  question.add_response(option=first_option, selected=True, comment="Don't panic")
                  question.add_response(option=last_option, selected=True)
              elif question.type == ResponseOption.TYPE_DATE:
                  # For date questions, pass a 'date' keyword argument.
                  question.add_response(date="2026-01-15")
          # Because we're directly setting a command_uuid, we can return both originate and edit.
          return [command.originate(), command.edit()]
    ```
####  Explanation 
  - **Retrieving Questions:** The `questions` property returns a list of question objects created from the questionnaire's data.
  - **Recording Responses:** Either set `answers` and let the command resolve each response against its question, or record them one at a time. Each question object provides an `add_response()` method that enforces the correct response format: 
    - For **TextQuestion** , you must pass a `text` parameter.
    - For **IntegerQuestion** , you must pass an `integer` parameter.
    - For **RadioQuestion** , you must pass an `option` parameter (a `ResponseOption` instance) that corresponds to one of the allowed options.
    - For **CheckboxQuestion** , you must pass an `option` parameter along with an optional `selected` flag (defaulting to True) and an optional `comment`. Multiple responses can be recorded for checkbox questions.
    - **Note for Checkboxes:** Only the responses explicitly provided in the command payload will be updated in the UI. If a checkbox response is already selected and is not sent as unselected in the payload, its state remains unchanged.
    - For **DateQuestion** , you must pass a `date` parameter (a `datetime.date`, a `datetime.datetime`, or an ISO 8601 date string), stored as a normalized `YYYY-MM-DD` string.
  - **Creating and Editing:** When creating a new questionnaire command, you must explicitly set a unique `command_uuid`. Providing this UUID enables you to originate the command within the note and then subsequently edit it with detailed responses in the same protocol execution.
  - This approach is necessary because given the dynamic nature of the questionnaire command, the initial creation (origination) only includes the questionnaire ID. Once the command has been originated, you can immediately follow up with an edit to populate it with the patient's responses.
  - If you are looking to insert a committed questionnaire command, you'll need to return three effects: 
    - An `.originate()` to insert the command and select the questionnaire
    - An `.edit()` to populate the responses
    - A `.commit()` to commit the command
* * *
###  ReasonForVisit 
**Command-specific parameters** :
Name | Type | Required | Description  
---|---|---|---  
`structured` | _boolean_ | `false` | Whether the RFV is structured or not. Defaults to False.  
`coding` | _Coding_ or _UUID (str)_ | `true` if structured=True | The coding for the structured RFV. Either a full Coding object (with `code`, `system`, `display`) or a UUID string referencing a verified coding record. If a Coding is provided, it is validated against existing [ReasonForVisitSettingCoding](/sdk/data-reason-for-visit/#reasonforvisitsettingcoding) records  
`comment` | _string_ | `false` | Additional commentary on the RFV.  
**Example** :
    ```python
    from canvas_sdk.commands import ReasonForVisitCommand
    structured_rfv = ReasonForVisitCommand(
      note_uuid='8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47',
      structured=True,
      coding={'code': '49727002', 'system': 'http://snomed.info/sct', 'display': 'Cough'},
      comment='also wants to discuss treatment options'
    )
    # Example with a UUID string referencing a Coding record
    structured_rfv2 = ReasonForVisitCommand(
      note_uuid='8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47',
      structured=True,
      coding="e2b1e1e3-3f52-4a0a-bb3a-123456789abc",  # Must correspond to an existing coding record
      comment="Discuss treatment options"
    )
    unstructured_rfv = ReasonForVisitCommand(
      note_uuid='8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47',
      comment='also wants to discuss treatment options'
    )
    ```
###  Refer 
**Command-specific parameters** :
Name | Type | Required to delegate / sign | Description  
---|---|---|---  
`service_provider` | _ServiceProvider_ | `true` | The service provider associated with the referral command. Search with the [contacts endpoint](/sdk/utils/#searching-for-contacts-and-service-providers).  
`diagnosis_codes` | _list[string]_ | `true` | A list of relevant ICD-10 Diagnosis. Search with the [ICD-10 condition endpoint](/sdk/utils/#get-icdcondition--icd-10-conditions).  
`clinical_question` | _ClinicalQuestion enum_ | `true` | The clinical question prompting the referral. Must be one of `ReferCommand.ClinicalQuestion`  
`priority` | _Priority enum_ | `false` | Priority of the imaging order. Must be one of `ReferCommand.Priority`.  
`notes_to_specialist` | _string_ | `true` | Notes or additional information directed to the specialist.  
`include_visit_note` | _boolean_ | `false` | Flag indicating whether the visit note should be included in the referral.  
`comment` | _string_ | `false` | An optional comment providing further details about the referral.  
`linked_items_urns` | _list[string]_ | `false` | List of URNs for items linked to the referral command.  
**Command-specific actions** :
Action Name | Available When | Description  
---|---|---  
`delegate_action` | command is staged | Delegates the order by creating a task.  
`sign_action` | command is staged | Signs the order, transitioning it from staged to committed state.  
`print_specialist` | command is committed | Prints the order using a specialist-focused template.  
`print_patient` | command is committed | Prints the order using a patient-friendly template.  
`fax` | command is committed | Transmits the order electronically via fax.  
**Enums and Types** :
**`Priority`**
Priority | Value | Description  
---|---|---  
`ROUTINE` | `"Routine"` | A routine referral.  
`URGENT` | `"Urgent"` | An urgent referral.  
`STAT` | `"STAT"` | A STAT (immediate) referral.  
**`ClinicalQuestion`**
Clinical Question | Value | Description  
---|---|---  
`COGNITIVE_ASSISTANCE` | `"Cognitive Assistance (Advice/Guidance)"` | Cognitive assistance (advice/guidance).  
`ASSISTANCE_WITH_ONGOING_MANAGEMENT` | `"Assistance with Ongoing Management"` | Assistance with ongoing management.  
`SPECIALIZED_INTERVENTION` | `"Specialized intervention"` | Specialized intervention.  
`DIAGNOSTIC_UNCERTAINTY` | `"Diagnostic Uncertainty"` | Diagnostic uncertainty.  
**Example** :
    ```python
    from canvas_sdk.commands import ReferCommand
    from canvas_sdk.commands.constants import ServiceProvider
    refer_command = ReferCommand(
        note_uuid="8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47",
        diagnosis_codes=["E119"],
        priority=ReferCommand.Priority.ROUTINE,
        clinical_question=ReferCommand.ClinicalQuestion.DIAGNOSTIC_UNCERTAINTY,
        comment="this is a comment",
        notes_to_specialist="This is a note to specialist",
        include_visit_note=True,
        service_provider=ServiceProvider(
          first_name="Clinic",
          last_name="Acupuncture",
          practice_name="Clinic Acupuncture",
          specialty="Acupuncture",
          business_address="Street Address",
          business_phone="1234569874",
          business_fax="1234569874"
     ),
    )
    ```
* * *
###  Reference 
Embeds a diagnostic view in the note. A diagnostic view is a saved combination of lab tests and questionnaire codes configured on your instance; referencing one renders that patient's results for those codes as a timeseries inside the note, so a reviewer sees the trend without leaving the chart.
The command renders as a read-only table. There are no fields for a user to fill in, so the diagnostic view has to be chosen by whatever inserts the command — a user can only commit or delete it, and enter it in error once committed.
Unlike ChartSectionReview, it is not committed on origination: it stays staged until you pass `commit=True` to `originate()` or send a separate `commit()`.
> **Warning:** The rendered name and table are derived from the diagnostic view when the command is originated, and are not recalculated afterwards. Pointing an existing command at a different diagnostic view with `edit()` leaves the previous view's name and table on display. To change the view, delete the command and originate a new one. 
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`diagnostic_view_id` | _UUID_ or _string_ | `true` | The id of the [DiagnosticView](/sdk/data-diagnostic-view/#diagnosticview) to embed. An id that does not match a diagnostic view on the instance is discarded, leaving the command with no view to render.  
**Example** :
    ```python
    from canvas_sdk.commands import ReferenceCommand
    from canvas_sdk.v1.data import DiagnosticView
    def compute():
        a1c_view = DiagnosticView.objects.filter(name="Hemoglobin A1c").first()
        if not a1c_view:
            return []
        reference = ReferenceCommand(
            note_uuid="8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47",
            diagnostic_view_id=a1c_view.id,
        )
        return [reference.originate(commit=True)]
    ```
* * *
###  ReferralReview 
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`report_ids` | _list[string]_ | `true` | List of [ReferralReport](/sdk/data-referral/#referralreport) IDs to review. Must be reports on that patient's chart.  
`message_to_patient` | _string_ | `false` | Message to communicate findings to the patient.  
`communication_method` | _ReportReviewCommunicationMethod enum_ | `false` | Method for patient communication. Must be one of `ReportReviewCommunicationMethod`.  
`linked_items_urns` | _list[string]_ | `false` | List of URNs for items linked to the review.  
`comment` | _string_ | `false` | Internal comment about the review.  
**Example** :
    ```python
    from canvas_sdk.commands import ReferralReviewCommand
    from canvas_sdk.commands.commands.review import ReportReviewCommunicationMethod
    from canvas_sdk.v1.data import Patient, ReferralReport
    patient = Patient.objects.get(id="patient-id")
    # Get referral reports to review
    referral_reports = ReferralReport.objects.filter(patient=patient, review__isnull=True, review_mode='RR')
    report_ids = [str(report.id) for report in referral_reports]
    referral_review = ReferralReviewCommand(
        note_uuid="a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d",
        report_ids=report_ids,
        message_to_patient="Your referral has been reviewed and approved.",
        communication_method=ReportReviewCommunicationMethod.DELEGATED_CALL_CAN_LEAVE_MESSAGE,
        comment="Referral approved, patient notified."
    )
    ```
* * *
###  Refill 
**Command-specific parameters** :
Check the Prescribe command for the parameters used in the Refill command. Refill supports `send()` under the same electronic prescribing validations.
**Example** :
    ```python
    from canvas_sdk.commands import RefillCommand, PrescribeCommand
    from canvas_sdk.commands.constants import ClinicalQuantity
    RefillCommand(
        fdb_code="216092",
        icd10_codes=["R51"],
        sig="Take one tablet daily after meals",
        days_supply=30,
        quantity_to_dispense=30,
        type_to_dispense=ClinicalQuantity(
            representative_ndc="12843016128",
            ncpdp_quantity_qualifier_code="C48542"
        ),
        refills=3,
        substitutions=PrescribeCommand.Substitutions.ALLOWED,
        pharmacy="pharmacy_ncpdp_id",
        prescriber_id="a7c2e9d1-3b4f-4a6c-8e0d-5f1a2b3c4d5e",
        supervising_provider_id="c3d4e5f6-7a8b-4c9d-0e1f-2a3b4c5d6e7f",
        note_to_pharmacist="Please verify patient's insurance before processing."
    )
    ```
* * *
###  RemoveAllergy 
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`allergy_id` | _string_ | `true` | The id of the [AllergyIntolerance](/sdk/data-allergy-intolerance/#allergyintolerance) to remove. Must be an allergy already recorded on that patient's chart.  
`narrative` | _string_ | `false` | Additional context or narrative for the removal (max length: 512 characters).  
**Example** :
    ```python
    from canvas_sdk.commands import RemoveAllergyCommand
    RemoveAllergyCommand(
        allergy_id="e5f6a7b8-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
        narrative="Allergy no longer applies after reassessment."
    )
    ```
* * *
###  Resolve Condition 
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`condition_id` | _string_ | `true` | The id of the [Condition](/sdk/data-condition/#condition) being resolved. Must be an **active** condition on that patient's chart — committed, not entered in error, and not already resolved.  
`show_in_condition_list` | _boolean_ | `false` | Determines whether the condition remains visible in patient chart summary.  
`rationale` | _string_ | `false` | Additional context.  
    ```python
    from canvas_sdk.commands.commands.resolve_condition import ResolveConditionCommand
    from canvas_sdk.v1.data import Condition
    patient_id = '<a patient ID from your instance>'
    patient_condition = Condition.objects.for_patient(patient_id).committed().active().first()
    ResolveConditionCommand(
       condition_id=patient_condition.id,
       show_in_condition_list=True,
       rationale="Additional notes.",
       note_uuid="8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47",
    )
    ```
* * *
###  Review of Systems 
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`questionnaire_id` | _string_ | `true` | The id of the [Questionnaire](/sdk/data-questionnaire/#questionnaire) being answered by the patient.  
`answers` | _list ofAnswer_ | `false` | The responses to record, one per question. Defaults to an empty list.  
####  Toggle Questions Feature 
The ReviewOfSystemsCommand supports the same question-toggling functionality as the PhysicalExamCommand, allowing practitioners to enable or disable specific system-review questions based on patient relevance. The available methods (`is_question_enabled`, `set_question_enabled`) and the `question_toggles` property are documented once under the PhysicalExam Toggle Questions Feature — they behave identically here.
**Example** :
    ```python
    from canvas_sdk.commands import ReviewOfSystemsCommand
    # Create a new review of systems
    ros = ReviewOfSystemsCommand(
      note_uuid='8a18931a-acd9-474b-9070-ccd6fd472313',
      questionnaire_id='ed92577b-a023-4370-bc85-2b57e8afc4d8',
    )
    questions = ros.questions  # Retrieve the list of questions
    # Returns: [
    #               Question(
    #                       self.name='question-14',
    #                       self.label='Recurrent fever or chills',
    #                       self.type='TXT',
    #                       self.options=[]],
    #                       self.response=None
    #               ),
    #               Question(
    #                       self.name='question-25',
    #                       self.label='Other',
    #                       self.type='TXT', self.options=[],
    #                       self.response=None
    #               )
    # Check if a question is enabled
    if ros.is_question_enabled("14"):
      print("Recurrent fever or chills question is enabled.")
    # Disable irrelevant questions
    ros.set_question_enabled("25", False)
    # Get all toggle states
    states = ros.question_toggles
    # Returns: {"14": True, "25": False, "26": True, ...}, where keys are question IDs and values are enabled states.
    # Working with existing ros - toggle states are preserved
    existing_ros = ReviewOfSystemsCommand(command_uuid='d4e5f6a7-8b9c-4d0e-1f2a-3b4c5d6e7f80')
    # All previously set toggle states are automatically loaded
    ```
**Note:** The ReviewOfSystemsCommand is a subclass of the QuestionnaireCommand, so it supports all the questionnaire features. That includes recording responses either with the `answers` parameter or with the `questions` property and `add_response()` — see Recording responses.
* * *
###  StopMedication 
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`medication_id` | _string_ | `true` | The id of the [Medication](/sdk/data-medication/#medication) being stopped. Must be a medication already recorded on that patient's chart.  
`rationale` | _string_ | `false` | The reason for stopping the medication.  
**Example** :
    ```python
    from canvas_sdk.commands import StopMedicationCommand
    stop_medication = StopMedicationCommand(
        note_uuid='8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47',
        medication_id='f0a1b2c3-d4e5-4f6a-8b9c-0d1e2f3a4b5c',
        rationale='In remission'
    )
    ```
* * *
###  StructuredAssessment 
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`questionnaire_id` | _string_ | `true` | The id of the [Questionnaire](/sdk/data-questionnaire/#questionnaire) being answered by the patient.  
`answers` | _list ofAnswer_ | `false` | The responses to record, one per question. Defaults to an empty list.  
**Note:** The StructuredAssessmentCommand is a subclass of the QuestionnaireCommand, so it supports all the questionnaire features. That includes recording responses either with the `answers` parameter or with the `questions` property and `add_response()` — see Recording responses.
**Example** :
    ```python
    from canvas_sdk.commands import StructuredAssessmentCommand
    questionnaire = StructuredAssessmentCommand(
        note_uuid='8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47',
        questionnaire_id='c1a2b3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d'
    )
    ```
* * *
###  Task 
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`title` | _string_ | `true` | The title or summary of the task.  
`assign_to` | _TaskAssigner_ | `true` | Specifies the assignee (role, team, or individual).  
`due_date` | _date_ | `false` | Due date for completing the task.  
`priority` | _TaskPriority enum_ | `false` | Priority of the task. Must be one of `TaskPriority`.  
`comment` | _string_ | `false` | Additional comments or notes about the task.  
`labels` | _list[string]_ | `false` | Labels to apply to the task. Each value is matched (case-insensitive) against an existing [TaskLabel](/sdk/data-task/#tasklabel) by name; values that don't match an existing label are ignored.  
`linked_items_urns` | _list[string]_ | `false` | URNs for items linked to the task.  
**Enums and Types** :
**`TaskPriority`**
Priority | Description  
---|---  
`STAT` | The request should be actioned immediately — highest possible priority. E.g. an emergency.  
`URGENT` | The request should be actioned promptly — higher priority than routine.  
`ROUTINE` | The request has normal priority.  
**TaskAssigner Type** :
Key | Type | Required | Description  
---|---|---|---  
`to` | _AssigneeType_ | `true` | Type of assignee (e.g., role, team, etc.).  
`id` | _integer_ | `false` | The `dbid` of the assignee, in the table selected by `to`: a [CareTeamRole](/sdk/data-care-team/#careteamrole) when `to` is `ROLE`, a [Team](/sdk/data-team/#team) when `to` is `TEAM`, or a [Staff](/sdk/data-staff/#staff) when `to` is `STAFF`. Omit when `to` is `UNASSIGNED`.  
AssigneeType | Value | Description  
---|---|---  
`ROLE` | `"role"` | Task assigned to a specific [CareTeamRole](/sdk/data-care-team/#careteamrole) (`id` is the role's `dbid`).  
`TEAM` | `"team"` | Task assigned to a specific [Team](/sdk/data-team/#team) (`id` is the team's `dbid`).  
`UNASSIGNED` | `"unassigned"` | Task is unassigned.  
`STAFF` | `"staff"` | Task assigned to a specific [Staff](/sdk/data-staff/#staff) member (`id` is the staff member's `dbid`).  
**Example** :
    ```python
    from canvas_sdk.commands import TaskCommand
    from canvas_sdk.commands.commands.task import TaskAssigner, AssigneeType
    from canvas_sdk.v1.data.task import TaskPriority
    from datetime import date
    TaskCommand(
        title="Follow-up appointment scheduling",
        assign_to=TaskAssigner(to=AssigneeType.STAFF, id=123),
        due_date=date(2024, 12, 15),
        priority=TaskPriority.URGENT,
        comment="Ensure the patient schedules a follow-up within 30 days.",
        labels=["Urgent"],
        linked_items_urns=["urn:task:123", "urn:note:456"]
    )
    ```
* * *
###  UncategorizedDocumentReview 
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`report_ids` | _list[string]_ | `true` | List of [UncategorizedClinicalDocument](/sdk/data-uncategorized-clinical-document/#uncategorizedclinicaldocument) ids to review. Must be documents already on that patient's chart.  
`message_to_patient` | _string_ | `false` | Message to communicate findings to the patient.  
`communication_method` | _ReportReviewCommunicationMethod enum_ | `false` | Method for patient communication. Must be one of `ReportReviewCommunicationMethod`.  
`linked_items_urns` | _list[string]_ | `false` | List of URNs for items linked to the review.  
`comment` | _string_ | `false` | Internal comment about the review.  
**Example** :
    ```python
    from canvas_sdk.commands import UncategorizedDocumentReviewCommand
    from canvas_sdk.v1.data import UncategorizedClinicalDocument, Patient
    from canvas_sdk.commands.commands.review import ReportReviewCommunicationMethod
    patient = Patient.objects.last()
    # Get uncategorized documents to review
    uncategorized_documents = UncategorizedClinicalDocument.objects.filter(patient=patient, review__isnull=True, review_mode='RR')
    report_ids = [str(doc.id) for doc in uncategorized_documents]
    uncategorized_review = UncategorizedDocumentReviewCommand(
        note_uuid="a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d",
        report_ids=report_ids,
        message_to_patient="Your document has been reviewed.",
        communication_method=ReportReviewCommunicationMethod.DELEGATED_CALL_CAN_LEAVE_MESSAGE,
        comment="Document reviewed, no further action needed."
    )
    ```
* * *
###  UpdateDiagnosis 
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`condition_code` | _string_ | `true` | The ICD-10 code of the existing diagnosis to update. Must match a [Condition](/sdk/data-condition/#condition) already on that patient's chart.  
`new_condition_code` | _string_ | `true` | The new ICD-10 code to replace the existing diagnosis, looked up via [`GET /icd/condition/`](/sdk/utils/#get-icdcondition--icd-10-conditions).  
`background` | _string_ | `false` | Background information or notes related to the updated diagnosis (max length: 2048 characters).  
`narrative` | _string_ | `false` | A narrative or explanation about the update (max length: 2048 characters).  
* * *
**Example**
    ```python
    from canvas_sdk.commands import UpdateDiagnosisCommand
    UpdateDiagnosisCommand(
        condition_code="E119",
        new_condition_code="E109",
        background="Patient previously diagnosed with diabetes type 2; now updated to diabetes type 1.",
        narrative="Updating condition based on recent clinical findings."
    )
    ```
* * *
###  UpdateGoal 
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`goal_id` | _string_ | `true` | The `id` of the [Goal](/sdk/data-goal/#goal) being updated. Must be a goal on that patient's chart.  
`due_date` | _datetime_ | `false` | The date the goal is due.  
`achievement_status` | _AchievementStatus enum_ | `false` | The current achievement status of the goal.  
`priority` | _Priority enum_ | `false` | The priority of the goal.  
`progress` | _string_ | `false` | A narrative about the patient's progress toward the goal.  
`AchievementStatus` | Value | Description  
---|---|---  
`IN_PROGRESS` | `"in-progress"` | The goal is being pursued.  
`IMPROVING` | `"improving"` | Progress toward the goal is improving.  
`WORSENING` | `"worsening"` | Progress toward the goal is worsening.  
`NO_CHANGE` | `"no-change"` | No change in progress toward the goal.  
`ACHIEVED` | `"achieved"` | The goal has been achieved.  
`SUSTAINING` | `"sustaining"` | The achieved goal is being sustained.  
`NOT_ACHIEVED` | `"not-achieved"` | The goal was not achieved.  
`NO_PROGRESS` | `"no-progress"` | No progress has been made toward the goal.  
`NOT_ATTAINABLE` | `"not-attainable"` | The goal is not attainable.  
`Priority` | Value | Description  
---|---|---  
`HIGH` | `"high-priority"` | High priority.  
`MEDIUM` | `"medium-priority"` | Medium priority.  
`LOW` | `"low-priority"` | Low priority.  
**Example** :
    ```python
    from canvas_sdk.commands import UpdateGoalCommand, GoalCommand
    from datetime import datetime
    update_goal = UpdateGoalCommand(
        note_uuid='8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47',
        goal_id='b7c8d9e0-1a2b-4c3d-5e6f-7a8b9c0d1e2f',
        due_date=datetime(2025, 3, 31),
        achievement_status=GoalCommand.AchievementStatus.WORSENING,
        priority=GoalCommand.Priority.MEDIUM,
        progress='patient has slowed down progress and requesting to move due date out'
    )
    ```
* * *
###  Vitals 
**Command-specific parameters** :
Name | Type | Required to commit | Description  
---|---|---|---  
`height` | _integer_ | `false` | Height in inches.  
`weight_lbs` | _integer_ | `false` | Weight in pounds.  
`weight_oz` | _integer_ | `false` | Weight in ounces.  
`waist_circumference` | _integer_ | `false` | Waist circumference in inches.  
`body_temperature` | _float_ | `false` | Body temperature in Fahrenheit.  
`body_temperature_site` | _BodyTemperatureSite_ | `false` | Site of body temperature measurement.  
`blood_pressure_systole` | _integer_ | `false` | Systolic blood pressure.  
`blood_pressure_diastole` | _integer_ | `false` | Diastolic blood pressure.  
`blood_pressure_position_and_site` | _BloodPressureSite_ | `false` | Position and site of blood pressure measurement.  
`pulse` | _integer_ | `false` | Pulse rate in beats per minute.  
`pulse_rhythm` | _PulseRhythm_ | `false` | Rhythm of the pulse.  
`respiration_rate` | _integer_ | `false` | Respiration rate in breaths per minute.  
`oxygen_saturation` | _integer_ | `false` | Oxygen saturation in percentage.  
`supplemental_oxygen` | _SupplementalOxygen_ | `false` | Type of supplemental oxygen the patient is receiving.  
`note` | _string_ | `false` | Additional notes (max length: 150 characters).  
**Enums and Types** :
BodyTemperatureSite | Value | Description  
---|---|---  
`AXILLARY` | `0` | Measurement taken from the armpit.  
`ORAL` | `1` | Measurement taken from the mouth.  
`RECTAL` | `2` | Measurement taken from the rectum.  
`TEMPORAL` | `3` | Measurement taken from the forehead.  
`TYMPANIC` | `4` | Measurement taken from the ear.  
BloodPressureSite | Value | Description  
---|---|---  
`SITTING_RIGHT_UPPER` | `0` | Sitting position, right upper arm.  
`SITTING_LEFT_UPPER` | `1` | Sitting position, left upper arm.  
`STANDING_RIGHT_UPPER` | `4` | Standing position, right upper arm.  
`SUPINE_LEFT_LOWER` | `11` | Supine position, left lower arm.  
PulseRhythm | Value | Description  
---|---|---  
`REGULAR` | `0` | Regular rhythm.  
`IRREGULARLY_IRREGULAR` | `1` | Completely irregular rhythm.  
`REGULARLY_IRREGULAR` | `2` | Regularly irregular rhythm.  
SupplementalOxygen | Value | Description  
---|---|---  
`CONTINUOUS_HIGH_FLOW` | `"LA28684-1"` | Continuous high-flow supplemental oxygen.  
`CONTINUOUS_LOW_FLOW` | `"LA28685-8"` | Continuous low-flow supplemental oxygen.  
`INTERMITTENT` | `"LA28686-6"` | Intermittent supplemental oxygen.  
**Example** :
    ```python
    from canvas_sdk.commands import VitalsCommand
    VitalsCommand(
        height=70,
        weight_lbs=150,
        body_temperature=98,
        body_temperature_site=VitalsCommand.BodyTemperatureSite.ORAL,
        blood_pressure_systole=120,
        blood_pressure_diastole=80,
        blood_pressure_position_and_site=VitalsCommand.BloodPressureSite.SITTING_RIGHT_UPPER,
        pulse=72,
        pulse_rhythm=VitalsCommand.PulseRhythm.REGULAR,
        oxygen_saturation=98,
        supplemental_oxygen=VitalsCommand.SupplementalOxygen.INTERMITTENT,
        note="Vitals are within normal range."
    )
    ```
##  Command Constants 
The `canvas_sdk.commands.constants` module provides essential classes and enumerations used across various Canvas SDK command implementations. These constants ensure consistency and provide structured data types for common medical and administrative elements.
###  ClinicalQuantity 
`ClinicalQuantity` represents detailed information about the form or unit of medication, particularly for prescription-related commands.
Field Name | Type | Required | Description  
---|---|---|---  
`representative_ndc` | _string_ | `true` | National Drug Code (NDC) representing the medication.  
`ncpdp_quantity_qualifier_code` | _string_ | `true` | NCPDP code indicating the quantity qualifier.  
`description` | _string_ | `false` | The clinical quantity description to dispense (e.g. `"0.5 mL vial"`). Use this field to narrow the selection to the correct clinical quantity when multiple options are available for the same NDC and qualifier code. If omitted, the first available clinical quantity is used.  
These values come from the `clinical_quantities` array returned by the [medication search](/sdk/utils/#searching-for-medications): `representative_ndc` ← `representative_ndc`, `ncpdp_quantity_qualifier_code` ← `erx_ncpdp_script_quantity_qualifier_code`, and `description` ← `clinical_quantity_description`.
**Usage Example** :
    ```python
    from canvas_sdk.commands import PrescribeCommand
    from canvas_sdk.commands.constants import ClinicalQuantity
    # Without description — selects the first available clinical quantity
    clinical_quantity = ClinicalQuantity(
        representative_ndc="12843016128",
        ncpdp_quantity_qualifier_code="C48542"
    )
    # With description — narrows to the correct clinical quantity when multiple options share the same NDC and qualifier code
    clinical_quantity = ClinicalQuantity(
        representative_ndc="00002024304",
        ncpdp_quantity_qualifier_code="C28254",
        description="0.5 mL vial"
    )
    prescribe = PrescribeCommand(
        note_uuid="8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47",
        fdb_code="216092",
        icd10_codes=["R51"],
        sig="Take one tablet daily after meals",
        days_supply=30,
        quantity_to_dispense=30,
        type_to_dispense=clinical_quantity,
        refills=3,
        substitutions=PrescribeCommand.Substitutions.ALLOWED
    )
    ```
###  ServiceProvider 
`ServiceProvider` represents detailed information about healthcare service providers, used in referral and imaging order commands.
Field Name | Type | Description  
---|---|---  
`first_name` | _string_ | Service provider's first name (max length 512)  
`last_name` | _string_ | Service provider's last name (max length 512)  
`specialty` | _string_ | Provider's specialty (max length 512)  
`practice_name` | _string_ | Name of the practice (max length 512)  
`business_fax` | _Optional[string]_ | Business fax number (optional, max length 512)  
`business_phone` | _Optional[string]_ | Business phone number (optional, max length 512)  
`business_address` | _Optional[string]_ | Business address (optional, max length 512)  
`notes` | _Optional[string]_ | Additional notes (optional, max length 512)  
**Usage Example** :
    ```python
    from canvas_sdk.commands import ReferCommand
    from canvas_sdk.commands.constants import ServiceProvider
    # Creating a referral with service provider information
    service_provider = ServiceProvider(
        first_name="John",
        last_name="Smith",
        specialty="Cardiology",
        practice_name="Heart Health Center",
        business_phone="555-0123",
        business_address="123 Medical Plaza, Suite 100"
    )
    refer = ReferCommand(
        note_uuid="8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47",
        diagnosis_codes=["E119"],
        priority=ReferCommand.Priority.ROUTINE,
        clinical_question=ReferCommand.ClinicalQuestion.DIAGNOSTIC_UNCERTAINTY,
        notes_to_specialist="Patient needs cardiac evaluation",
        service_provider=service_provider
    )
    ```
###  CodeSystems 
`CodeSystems` provides standardized medical coding system identifiers used throughout Canvas for consistent medical code classification.
**Available Code Systems** :
Code System | System URI | Description  
---|---|---  
`SNOMED` | `http://snomed.info/sct` | Systematized Nomenclature of Medicine Clinical Terms  
`RXNORM` | `http://www.nlm.nih.gov/research/umls/rxnorm` | RxNorm — standardized nomenclature for medications  
`LOINC` | `http://loinc.org` | Logical Observation Identifiers Names and Codes (labs/observations)  
`FDB` | `http://www.fdbhealth.com/` | First Databank drug knowledge base  
`ICD10` | `ICD-10` | International Classification of Diseases, 10th Revision  
`CVX` | `http://hl7.org/fhir/sid/cvx` | CDC codes for administered vaccines  
`CPT` | `http://www.ama-assn.org/go/cpt` | Current Procedural Terminology (AMA procedure codes)  
`NUCC` | `http://www.nucc.org/` | National Uniform Claim Committee provider taxonomy codes  
`NDC` | `http://hl7.org/fhir/sid/ndc` | National Drug Code  
`HCPCS` | `http://www.cms.gov/medicare/coding/medhcpcsgeninfo` | Healthcare Common Procedure Coding System  
`UNITS_OF_MEASURE` | `http://unitsofmeasure.org` | Unified Code for Units of Measure (UCUM)  
`FULLSCRIPT` | `http://fullscript.com` | Fullscript supplement/dispensary code system  
`UNSTRUCTURED` | `UNSTRUCTURED` | Canvas-specific system for unstructured or custom codes  
**Usage Example** :
    ```python
    from canvas_sdk.commands.constants import CodeSystems, Coding
    # Using different code systems
    icd10_coding = Coding(
        system=CodeSystems.ICD10, 
        code="E11.9", 
        display="Type 2 diabetes mellitus without complications"
    )
    snomed_coding = Coding(
        system=CodeSystems.SNOMED, 
        code="65921008", 
        display="Drink plenty of fluids"
    )
    unstructured_coding = Coding(
        system=CodeSystems.UNSTRUCTURED, 
        code="Custom instruction text"
    )
    ```
###  Coding 
`Coding` represents a coded value from a medical terminology system, providing structured representation of medical concepts.
Field Name | Type | Description  
---|---|---  
`system` | _string_ | The coding system identifier (e.g., ICD-10, SNOMED)  
`code` | _string_ | The specific code within the system  
`display` | _Optional[string]_ | Human-readable description of the code  
**Usage Example** :
    ```python
    from canvas_sdk.commands import InstructCommand
    from canvas_sdk.commands.constants import CodeSystems, Coding
    # Using structured coding with SNOMED
    instruct_snomed = InstructCommand(
        note_uuid="8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47",
        coding=Coding(
            system=CodeSystems.SNOMED,
            code="65921008",
            display="Drink plenty of fluids"
        ),
        comment="To address mild dehydration symptoms"
    )
    # Using unstructured coding for custom instructions
    instruct_custom = InstructCommand(
        note_uuid="8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47",
        coding=Coding(
            system=CodeSystems.UNSTRUCTURED,
            code="Physical medicine neuromuscular training"
        )
    )
    ```
###  ReportReviewCommunicationMethod 
The `communication_method` value shared by the review commands — ImagingReview, LabReview, ReferralReview, and UncategorizedDocumentReview.
    ```python
    from canvas_sdk.commands.commands.review import ReportReviewCommunicationMethod
    ```
Communication Method | Value | Description  
---|---|---  
`DELEGATED_CALL_CAN_LEAVE_MESSAGE` | `"DM"` | Delegated call - can leave message  
`DELEGATED_CALL_NEED_ANSWER` | `"DA"` | Delegated call - need answer  
`DELEGATED_LETTER` | `"DL"` | Delegated letter to be sent to patient  
`ALREADY_LEFT_MESSAGE` | `"AM"` | Already left message for patient  
`ALREADY_REVIEWED_WITH_PATIENT` | `"AR"` | Already reviewed with patient  
----- END PAGE https://docs.canvasmedical.com/sdk/commands/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/companion/
The **Provider Companion** is a mobile-optimized, provider-facing web app that runs alongside Canvas. It's designed for the phone-in-hand moments in a clinician's day — looking something up between rooms, triaging a message list, checking in on a patient's tasks — rather than the sit-down charting workflows the desktop app covers.
Your plugins can contribute applications to the companion just like they do to the desktop. This page covers how plugins integrate, the three companion-specific application scopes, and how to share code when you want the same plugin to work across multiple scopes.
##  Accessing the companion 
The companion lives at `/companion/` on your Canvas instance:
    ```text
    https://<instance>.canvasmedical.com/companion/
    ```
Any staff user who can log in to the desktop Canvas app can log in to the companion with the same credentials. Patients don't have access — it's a provider surface only.
##  How plugins extend the companion 
Canvas plugins contribute embedded apps through the `Application` handler — a Python class with an `on_open()` method that returns a URL for Canvas to iframe into its UI. Which surface your app appears on is controlled by the `scope` value in your plugin's `CANVAS_MANIFEST.json`. Companion apps work exactly the same way; they just use one of three companion-specific `scope` values. If you haven't built an embedded app before, start with the [Applications](/sdk/handlers-applications/) page — this page assumes you know the basics and focuses on what's companion-specific.
There are three companion scopes:
Scope | Where the app shows up  
---|---  
`provider_companion_global` | Icon in the app launcher on the companion's main page, outside of any patient context  
`provider_companion_patient_specific` | Tab on the patient page, next to the built-in Timeline tab  
`provider_companion_note_specific` | Tab within an opened note on the patient page  
When a user opens your app, Canvas fires an `APPLICATION__ON_OPEN` event and your handler's `on_open()` runs. Return a `LaunchModalEffect` pointing at whatever URL you want embedded — typically a page served by a SimpleAPI handler in the same plugin.
##  `provider_companion_global`
Global-scope apps appear in the companion's launcher on the main page. They run with no patient or note context — they're the right surface for workflows that span many patients, or administrative work that isn't tied to a chart.
![](/assets/images/sdk/companion/companion-global.png) ![](/assets/images/sdk/companion/global-app.png)
###  Event context 
`self.event.context` contains the acting user but no patient or note keys.
###  Use cases 
  - A **task queue** showing every task assigned to the logged-in provider across all patients.
  - A **schedule viewer** for the day's appointments.
  - A **secure chat** for the care team.
  - An **administrative dashboard** — e.g. open lab orders awaiting review.
###  Example 
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.launch_modal import LaunchModalEffect
    from canvas_sdk.handlers.application import Application
    class TaskDashboardGlobal(Application):
        """Companion global app — every task for the logged-in provider."""
        def on_open(self) -> Effect:
            return LaunchModalEffect(
                url="/plugin-io/api/task_dashboard/app/tasks",
                target=LaunchModalEffect.TargetType.DEFAULT_MODAL,
            ).apply()
    ```
`CANVAS_MANIFEST.json`:
    ```json
    {
      "sdk_version": "0.1.4",
      "plugin_version": "0.0.1",
      "name": "task_dashboard",
      "description": "A task dashboard for the provider companion.",
      "components": {
        "applications": [
          {
            "class": "task_dashboard.applications.global_app:TaskDashboardGlobal",
            "name": "Tasks",
            "description": "All tasks assigned to me.",
            "scope": "provider_companion_global",
            "icon": "assets/tasks.png"
          }
        ],
        "handlers": [
          {
            "class": "task_dashboard.handlers.api:TaskDashboardAPI",
            "description": "Serves the task dashboard page the iframe loads."
          }
        ],
        "commands": [],
        "content": [],
        "effects": [],
        "views": []
      },
      "variables": [],
      "tags": {},
      "references": [],
      "license": "",
      "diagram": false,
      "readme": "./README.md"
    }
    ```
The `TaskDashboardAPI` class is the [SimpleAPI handler](/sdk/handlers-simple-api/) that actually serves the page `on_open` iframes. `on_open` only returns the launch effect; the page itself has to be served by something, and that something is a SimpleAPI handler registered here.
##  `provider_companion_patient_specific`
Patient-scope apps appear as tabs on the patient page. Your tab sits next to the built-in Timeline tab, and when the user taps it, your handler's `on_open()` fires with the patient in `event.context`.
![](/assets/images/sdk/companion/patient-timeline.png) ![](/assets/images/sdk/companion/patient-app.png)
###  Event context 
    ```python
    self.event.context["patient"]["id"]  # Patient id (UUID string)
    ```
###  Use cases 
  - A **chart summary** of conditions, meds, allergies, vitals, etc. for the open patient.
  - A **risk-score panel** tied to the patient.
  - A **care-plan checklist** for this patient's current programs.
  - A **patient-scoped task list** — the same tasks view as the global app, but filtered to just this patient's tasks.
###  Example 
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.launch_modal import LaunchModalEffect
    from canvas_sdk.handlers.application import Application
    class TaskDashboardPatient(Application):
        """Companion patient app — tasks filtered to one patient."""
        def on_open(self) -> Effect:
            patient_id = self.event.context.get("patient", {}).get("id", "")
            return LaunchModalEffect(
                url=f"/plugin-io/api/task_dashboard/app/tasks?patient_id={patient_id}",
                target=LaunchModalEffect.TargetType.DEFAULT_MODAL,
            ).apply()
    ```
`CANVAS_MANIFEST.json`:
    ```json
    {
      "sdk_version": "0.1.4",
      "plugin_version": "0.0.1",
      "name": "task_dashboard",
      "description": "A patient-scoped task dashboard for the provider companion.",
      "components": {
        "applications": [
          {
            "class": "task_dashboard.applications.patient_app:TaskDashboardPatient",
            "name": "Tasks",
            "description": "Tasks for this patient.",
            "scope": "provider_companion_patient_specific",
            "icon": "assets/tasks.png"
          }
        ],
        "handlers": [
          {
            "class": "task_dashboard.handlers.api:TaskDashboardAPI",
            "description": "Serves the task dashboard page the iframe loads."
          }
        ],
        "commands": [],
        "content": [],
        "effects": [],
        "views": []
      },
      "variables": [],
      "tags": {},
      "references": [],
      "license": "",
      "diagram": false,
      "readme": "./README.md"
    }
    ```
##  `provider_companion_note_specific`
Note-scope apps appear as tabs inside an opened note on the patient page. Use these for workflows scoped to a single encounter. Both the patient and the note are passed in the event context.
![](/assets/images/sdk/companion/note.png) ![](/assets/images/sdk/companion/note-app.png)
###  Event context 
    ```python
    self.event.context["patient"]["id"]  # Patient id (UUID string)
    self.event.context["note"]["id"]     # Note UUID
    ```
###  Use cases 
  - A **documentation assistant** that reads the note's commands and suggests improvements.
  - A **coding / billing helper** that computes E&M level from the note's content.
  - A **visit-specific questionnaire** the provider fills out per encounter.
  - An **inline scribe** that writes note content from dictation or an LLM.
###  Example 
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.launch_modal import LaunchModalEffect
    from canvas_sdk.handlers.application import Application
    class ScribeAssistant(Application):
        """Companion note app — inline scribe for the open note."""
        def on_open(self) -> Effect:
            patient_id = self.event.context.get("patient", {}).get("id", "")
            note_id = self.event.context.get("note", {}).get("id", "")
            return LaunchModalEffect(
                url=(
                    f"/plugin-io/api/scribe/app/compose"
                    f"?patient_id={patient_id}&note_id={note_id}"
                ),
                target=LaunchModalEffect.TargetType.DEFAULT_MODAL,
            ).apply()
    ```
`CANVAS_MANIFEST.json`:
    ```json
    {
      "sdk_version": "0.1.4",
      "plugin_version": "0.0.1",
      "name": "scribe",
      "description": "Inline scribe for the provider companion.",
      "components": {
        "applications": [
          {
            "class": "scribe.applications.note_app:ScribeAssistant",
            "name": "Scribe",
            "description": "Inline scribe for this note.",
            "scope": "provider_companion_note_specific",
            "icon": "assets/scribe.png"
          }
        ],
        "handlers": [
          {
            "class": "scribe.handlers.api:ScribeAPI",
            "description": "Serves the scribe page the iframe loads."
          }
        ],
        "commands": [],
        "content": [],
        "effects": [],
        "views": []
      },
      "variables": [],
      "tags": {},
      "references": [],
      "license": "",
      "diagram": false,
      "readme": "./README.md"
    }
    ```
###  Originating commands on the note 
The note scope's real payoff is that your app can contribute to the note it's running in. Take the `note_uuid` from the event context, build an SDK command with it, and return the command's `originate()` effect alongside your JSON response — the platform will materialize the command in the note after your handler returns.
    ```python
    from http import HTTPStatus
    from canvas_sdk.commands.commands.vitals import VitalsCommand
    from canvas_sdk.effects.simple_api import JSONResponse
    from canvas_sdk.handlers.simple_api import SimpleAPI, StaffSessionAuthMixin, api
    class VitalsEntryAPI(StaffSessionAuthMixin, SimpleAPI):
        PREFIX = "/app"
        @api.post("/vitals")
        def submit(self):
            note_id = self.request.query_params.get("note_id", "")
            body = self.request.json() or {}
            command = VitalsCommand(
                note_uuid=note_id,
                blood_pressure_systole=body.get("bp_sys"),
                blood_pressure_diastole=body.get("bp_dia"),
                pulse=body.get("pulse"),
            )
            return [
                command.originate(),
                JSONResponse(
                    {"status": "submitted"},
                    status_code=HTTPStatus.ACCEPTED,
                ),
            ]
    ```
The Application passes `note_id` through to the iframe on the launch URL's query string; the iframe sends it along on the POST to `/vitals`. The same pattern works for any SDK command that exposes an `originate()` method — assessments, prescriptions, lab orders, imaging orders, etc.
**Attribution is the plugin author's responsibility.** Commands don't carry an explicit originator field; the platform attributes them to whoever the authenticated session belongs to when the effect is applied. The [`StaffSessionAuthMixin`](/sdk/handlers-simple-api-http/#staff-session) on the handler above ensures the request is gated on a logged-in staff session, so the originated command is attributed to that staff user rather than a generic plugin service identity. If your handler doesn't enforce a staff session, commands it originates won't be tied to the provider using the app — gate every command-originating route with `StaffSessionAuthMixin` (or a stricter equivalent).
##  Sharing code across scopes 
You don't need a separate plugin per scope — a single plugin can register several applications, all backed by the same SimpleAPI handler and the same UI bundle. The Application subclasses differ only in which scope they declare and what they put in the launch URL's query string; the shared handler branches on what it receives.
The task dashboard is a natural example. The global view and the patient-scoped view show the same UI — a list of task cards — but one shows every task assigned to the provider and the other shows only tasks tied to the open patient. They can share everything except the scope declaration and one query-string parameter.
###  Manifest — two applications, one handler 
    ```json
    {
      "sdk_version": "0.1.4",
      "plugin_version": "0.0.1",
      "name": "task_dashboard",
      "description": "Task dashboard — global and patient-scoped from one codebase.",
      "components": {
        "applications": [
          {
            "class": "task_dashboard.applications.global_app:TaskDashboardGlobal",
            "name": "Tasks",
            "description": "All tasks assigned to me.",
            "scope": "provider_companion_global",
            "icon": "assets/tasks.png"
          },
          {
            "class": "task_dashboard.applications.patient_app:TaskDashboardPatient",
            "name": "Tasks",
            "description": "Tasks for this patient.",
            "scope": "provider_companion_patient_specific",
            "icon": "assets/tasks.png"
          }
        ],
        "handlers": [
          {
            "class": "task_dashboard.handlers.api:TaskDashboardAPI",
            "description": "Serves the task dashboard page and JSON bundle."
          }
        ],
        "commands": [],
        "content": [],
        "effects": [],
        "views": []
      },
      "variables": [],
      "tags": {},
      "references": [],
      "license": "",
      "diagram": false,
      "readme": "./README.md"
    }
    ```
###  Shared SimpleAPI handler 
The handler serves the same HTML for both scopes and branches on `patient_id` in its data endpoint: present → filter to that patient, absent → return the provider's entire task queue.
    ```python
    from http import HTTPStatus
    from canvas_sdk.effects.simple_api import HTMLResponse, JSONResponse
    from canvas_sdk.handlers.simple_api import SimpleAPI, StaffSessionAuthMixin, api
    from canvas_sdk.templates import render_to_string
    from canvas_sdk.v1.data.task import Task
    class TaskDashboardAPI(StaffSessionAuthMixin, SimpleAPI):
        PREFIX = "/app"
        @api.get("/tasks")
        def page(self):
            return [HTMLResponse(
                render_to_string("static/tasks.html"),
                status_code=HTTPStatus.OK,
            )]
        @api.get("/tasks/data.json")
        def data(self):
            user_id = self.request.headers["canvas-logged-in-user-id"]
            patient_id = self.request.query_params.get("patient_id")
            tasks = Task.objects.filter(assigned_to_id=user_id)
            if patient_id:
                tasks = tasks.filter(patient__key=patient_id)
            return [JSONResponse(
                {"tasks": [serialize(t) for t in tasks]},
                status_code=HTTPStatus.OK,
            )]
    ```
The HTML/JS bundle (served by `/app/tasks`) fetches `/app/tasks/data.json` relative to its own URL, so it gets the right slice of tasks without knowing which scope launched it — the scope is encoded in whether the Application handler appended `?patient_id=...` to the launch URL.
This pattern generalizes: any time the _same UI_ works on a filtered or unfiltered dataset, you can register one Application per scope and share the handler, templates, and client-side code underneath.
##  Dismissing your modal 
Shortly after your iframe loads, Canvas transfers a `MessagePort` to it via a `postMessage` event. The plugin stores that port and posts `{type: 'CLOSE_MODAL'}` through it whenever it wants to dismiss itself — typically right after a successful form submit, or from a Cancel button.
    ```javascript
    let messagePort = null;
    window.addEventListener('message', (event) => {
        if (event.data?.type === 'INIT_CHANNEL' && event.ports?.[0]) {
            messagePort = event.ports[0];
            messagePort.start();
        }
    });
    function closeModal() {
        if (messagePort) {
            messagePort.postMessage({ type: 'CLOSE_MODAL' });
        } else {
            window.close();  // fallback if the port never arrived
        }
    }
    ```
Register the `message` listener at module scope, not inside a `DOMContentLoaded` handler — the port can arrive before your DOM is ready, and a listener attached later will miss it.
##  Async effects from SimpleAPI handlers 
Effects returned from a SimpleAPI route execute **after** the handler returns — they're dispatched to a platform worker, not applied inside your handler's transaction. That means you can't emit e.g. `Patient(...).create()` and then query for the new patient in the same request — the effect hasn't been processed yet and the record doesn't exist.
If you need the new record's UUID (for example to deep-link to it), do the lookup on a follow-up request from the iframe. The simplest shape: `POST /create` emits the effect and returns `202 Accepted` with everything needed to identify the new record; the iframe polls a separate `GET /find` endpoint until the record appears (or a short timeout fires).
    ```python
    import datetime
    from http import HTTPStatus
    from canvas_sdk.effects.patient import Patient as PatientEffect
    from canvas_sdk.effects.simple_api import JSONResponse
    from canvas_sdk.handlers.simple_api import SimpleAPI, StaffSessionAuthMixin, api
    from canvas_sdk.v1.data.patient import Patient
    class RegisterPatientAPI(StaffSessionAuthMixin, SimpleAPI):
        PREFIX = "/app"
        @api.post("/create")
        def create(self):
            body = self.request.json() or {}
            # ... validate the submission ...
            effect = PatientEffect(
                first_name=body["first_name"],
                last_name=body["last_name"],
                birthdate=body["birth_date"],
            ).create()
            return [
                effect,
                JSONResponse({
                    "status": "submitted",
                    "lookup_params": {
                        "first_name": body["first_name"],
                        "last_name": body["last_name"],
                        "birth_date": body["birth_date"],
                    },
                    "lookup_started_at":
                        datetime.datetime.now(datetime.timezone.utc).isoformat(),
                }, status_code=HTTPStatus.ACCEPTED),
            ]
        @api.get("/find")
        def find(self):
            params = self.request.query_params
            found = (
                Patient.objects
                .filter(
                    first_name=params["first_name"],
                    last_name=params["last_name"],
                    birth_date=params["birth_date"],
                    created__gte=params["after"],
                )
                .order_by("-created")
                .first()
            )
            return [JSONResponse(
                {"patient_id": str(found.id) if found else None},
                status_code=HTTPStatus.OK,
            )]
    ```
On the iframe, poll `/find` at ~500 ms intervals for ~5 s. On the first hit, deep-link to the new record. On timeout, surface a clear error message — the effect failed and there's nothing to link to. Don't claim success before the lookup confirms the record exists.
##  Common patterns 
The conventions are the same as any other SDK application — the companion just chooses where and when to render your iframe.
  - **Serve your UI from a[SimpleAPI handler](/sdk/handlers-simple-api/)** in the same plugin. `on_open()` returns a `LaunchModalEffect` pointing at a URL like `/plugin-io/api/<plugin_name>/...`.
  - **Authenticate with[`StaffSessionAuthMixin`](/sdk/handlers-simple-api-http/#staff-session).** The companion is staff-only, so every request hitting your plugin's SimpleAPI should be gated on a valid staff session. Mix the class in instead of writing your own `authenticate()` — it rejects non-staff sessions (including patient-portal sessions) up front:
        ```python
        from canvas_sdk.handlers.simple_api import SimpleAPI, StaffSessionAuthMixin
        class TaskDashboardAPI(StaffSessionAuthMixin, SimpleAPI):
            PREFIX = "/app"
            # ... routes ...
        ```
The logged-in user is then available via `self.request.headers["canvas-logged-in-user-id"]`.
  - **Push live updates with a plugin-owned WebSocket.** If your app needs to stay in sync when data changes, add a `BaseHandler` that listens for the relevant domain events and broadcasts on a channel the iframe subscribes to. See [WebSocket API](/sdk/handlers-simple-api-websocket/) for the handler shape and authentication flow.
  - **Keep it mobile-first.** The companion runs on phones. System fonts, stacked sections, generous tap targets, no hover interactions — your app should feel native inside the companion's shell.
  - **Drop your own top chrome in patient and note scope.** The companion harness already renders the patient's name (and, in note scope, the note type and date) above your iframe. If your plugin also renders a title bar, the result is a doubled-up header. Suppress the iframe's header when running in patient or note scope — either branch in your Application and pass a hint through the launch URL, or scope the CSS on a body class your shell sets from the query string.
  - **Link out to another patient with`window.top.location`.** To navigate from inside your modal to another patient's companion view — for example, a "tap a patient name to jump there" pattern — set `window.top.location = "/companion/patient/<uuid>/"`. That tears down the iframe and replaces the parent page. Setting only the iframe's `location` leaves the modal open showing the patient page on top of whatever was behind it, which is rarely what you want.
##  Further reading 
  - [Applications](/sdk/handlers-applications/) — the base `Application` handler class, `on_open()`, and other application scopes.
  - [SimpleAPI](/sdk/handlers-simple-api/) — serving HTML and JSON from a plugin.
  - [WebSocket API](/sdk/handlers-simple-api-websocket/) — pushing live updates to an iframe.
  - [Data module](/sdk/data/) — read-only clinical data models.
  - Example plugin: [`example_provider_companion_app`](https://github.com/canvas-medical/canvas-plugins/tree/main/example-plugins/example_provider_companion_app) demonstrates one plugin registering apps at all three companion scopes.
----- END PAGE https://docs.canvasmedical.com/sdk/companion/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/custom-data-attribute-hubs/
##  Overview 
`AttributeHubs` provide a simple mechanism for storing arbitrary data that doesn't belong to existing models, or does not conform to a traditional database schema. An AttributeHub is merely a collection of named attributes with values. This approach is ideal for cross-cutting concerns that span multiple models, temporary data storage, external system state tracking, data model prototyping, or plugin-specific configuration.
**Best for:**
  - Cross-cutting state that spans multiple models (sync cursors, external IDs)
  - One-off or small-collection configuration and state
  - Data with no natural schema (varying fields per record)
  - External system state tracking
**Example use cases:**
  - API synchronization state
  - External system identifiers
  - Plugin configuration and feature flags
**Not ideal for** entities with relationships, large collections you need to search or paginate, or data requiring aggregation or reporting. See [Design Considerations](/sdk/custom-data-design-considerations/) for detailed guidance.
##  Creating an AttributeHub 
Create a hub for a specific purpose using the `type` and `id` fields, which together form a unique key. There is a database constraint on these two fields to ensure uniqueness, and creating a duplicate will raise a `UniqueViolation` exception.
    ```python
    from canvas_sdk.v1.data import AttributeHub
    # Create a hub for a specific purpose
    hub = AttributeHub.objects.create(
        type="staff_profile",
        id="staff_id:abc123"
    )
    ```
##  Storing Data in AttributeHub 
Store individual attributes or complex data as JSON. Here's an example of a meal tracker that records patient meals and calories:
    ```python
    from datetime import datetime
    from canvas_sdk.v1.data import AttributeHub, Patient
    patient = Patient.objects.get(id="patient-uuid-here")
    # Create a hub to track a specific meal
    hub = AttributeHub.objects.create(
        type="meal_entry",
        id=f"patient:{patient.id}:meal:{datetime.now().isoformat()}"
    )
    # Store individual attributes
    hub.set_attribute("meal_type", "lunch")
    hub.set_attribute("calories", 650)
    hub.set_attribute("recorded_at", datetime.now())
    # Store complex data as JSON
    meal_details = {
        "foods": [
            {"name": "Grilled chicken salad", "calories": 350, "protein_g": 35},
            {"name": "Whole grain roll", "calories": 150, "protein_g": 5},
            {"name": "Apple", "calories": 95, "protein_g": 0},
            {"name": "Water", "calories": 0, "protein_g": 0}
        ],
        "notes": "Patient reported feeling satisfied after meal"
    }
    hub.set_attribute("meal_details", meal_details)
    # Store multiple attributes at once
    hub.set_attributes({
        "total_protein_g": 40,
        "meal_location": "home",
        "logged_by": "patient_self_report"
    })
    ```
##  Retrieving Data from AttributeHub 
Use the get-or-create pattern to retrieve existing hubs or create new ones:
    ```python
    from canvas_sdk.v1.data import AttributeHub, Patient
    patient = Patient.objects.get(id="patient-uuid-here")
    # Get or create a hub for tracking daily calorie totals
    hub, created = AttributeHub.objects.get_or_create(
        type="daily_calorie_summary",
        id=f"patient:{patient.id}:date:2024-01-15"
    )
    if created:
        # Initialize a new day's tracking
        hub.set_attributes({
            "total_calories": 0,
            "meal_count": 0,
            "calorie_goal": 2000
        })
    # Retrieve attributes
    total_calories = hub.get_attribute("total_calories")
    meal_count = hub.get_attribute("meal_count")
    calorie_goal = hub.get_attribute("calorie_goal")
    # Handle missing attributes gracefully
    notes = hub.get_attribute("daily_notes")  # Returns None if not set
    ```
##  Supported Value Types 
Attributes are automatically stored in appropriately typed database columns. The column is selected based on the Python type of the value you pass to `set_attribute()`:
    ```python
    from datetime import date, datetime
    from canvas_sdk.v1.data import AttributeHub
    hub = AttributeHub.objects.get(type="staff_profile", id="staff_id:abc123")
    # String values
    hub.set_attribute("bio", "Board-certified cardiologist")
    # Integer values
    hub.set_attribute("patient_capacity", 100)
    # Boolean values
    hub.set_attribute("accepting_patients", True)
    # Decimal values
    hub.set_attribute("rating", 4.8)
    # Date values
    hub.set_attribute("creation_date", date.today())
    # Datetime values
    hub.set_attribute("last_updated", datetime.now())
    # JSON/Complex objects (dicts, lists)
    hub.set_attribute("preferences", {
        "notification_email": True,
        "notification_sms": False
    })
    ```
Field Name | Python Type | Django Field Type | PostgreSQL Data Type  
---|---|---|---  
`text_value` | `str` | `TextField` | `text`  
`int_value` | `int` | `IntegerField` | `integer`  
`bool_value` | `bool` | `BooleanField` | `boolean`  
`decimal_value` | `float`, `Decimal` | `DecimalField` | `decimal(20,10)`  
`date_value` | `date` | `DateField` | `date`  
`timestamp_value` | `datetime` | `DateTimeField` | `timestamp with time zone`  
`json_value` | `dict`, `list` | `JSONField` | `jsonb`  
These typed columns can be referenced directly in queries. See When to Use Explicit Field Names for cases where you need to target a specific column.
##  Querying AttributeHubs by Attribute Values 
Find AttributeHubs based on the values stored in their attributes using `custom_attributes__value`. The SDK automatically routes the filter to the correct typed column based on the Python type of the value you pass in:
    ```python
    from canvas_sdk.v1.data import AttributeHub
    # Find hubs with a specific string attribute
    lunch_hubs = AttributeHub.objects.filter(
        type="meal_entry",
        custom_attributes__name="meal_type",
        custom_attributes__value="lunch",
    )
    # Find hubs with a calorie count above a threshold
    high_calorie = AttributeHub.objects.filter(
        type="meal_entry",
        custom_attributes__name="calories",
        custom_attributes__value__gte=500,
    )
    # Find hubs with a boolean flag
    active_flags = AttributeHub.objects.filter(
        type="feature_flags",
        custom_attributes__name="enabled",
        custom_attributes__value=True,
    )
    ```
You can also filter attribute objects directly, for example when working with a hub's related attributes:
    ```python
    from canvas_sdk.v1.data import AttributeHub
    hub = AttributeHub.objects.get(type="meal_entry", id="patient:abc:meal:2024-01-15T12:00")
    # Filter the hub's own attributes
    high_cal_attrs = hub.custom_attributes.filter(value__gte=500)
    ```
###  When to Use Explicit Field Names 
In most cases `custom_attributes__value` (or `value` on a hub's related attributes) is sufficient. However, you must reference the typed column directly in the following cases:
  - **JSON containment queries.** PostgreSQL's `@>` containment operator on `jsonb` has different semantics from the `LIKE '%...%'` that `__contains` produces on a text column. Since `value__contains` with a string argument targets `text_value`, you must use `json_value__contains` to perform JSON containment checks:
        ```python
        from django.db.models import Q
        from canvas_sdk.v1.data import AttributeHub
        # Find hubs whose "specialties" JSON array contains "Cardiology"
        AttributeHub.objects.filter(
            type="staff_profile",
            custom_attributes__name="specialties",
            custom_attributes__json_value__contains="Cardiology",
        )
        # OR across multiple JSON values
        specialty_filters = Q()
        for specialty in ["Cardiology", "Internal Medicine"]:
            specialty_filters |= Q(custom_attributes__json_value__contains=specialty)
        AttributeHub.objects.filter(
            Q(custom_attributes__name="specialties") & specialty_filters
        )
        ```
  - **Custom JSON lookups.** Django's `JSONField` supports lookups like `__has_key`, `__contained_by`, and key-path access (`json_value__key__nested`). These are only available on the `json_value` column directly.
  - **Ambiguous Python types.** The `value` rewriter uses `type()` (not `isinstance()`) to select the column. If you pass a string but intend to query `json_value` (or vice versa), the rewriter will target the wrong column. Use the explicit field name when the Python type of your filter value doesn't match the storage column.
  - **Null checks across relations.** `custom_attributes__value=None` and `custom_attributes__value__isnull` are not supported on `AttributeHub.objects.filter(...)` and will raise `TypeError`. Null checks require testing every typed column, which produces unreliable results when combined with Django's cross-relation JOIN machinery. Use explicit column names instead:
        ```python
        from canvas_sdk.v1.data import AttributeHub
        # Check whether a specific column is null across the relation
        AttributeHub.objects.filter(
            type="staff_profile",
            custom_attributes__name="specialty",
            custom_attributes__text_value__isnull=True,
        )
        ```
Note that `value=None` and `value__isnull` _are_ supported for direct queries on a hub's own attributes (e.g., `hub.custom_attributes.filter(value__isnull=True)`), where no cross-relation join is involved.
Refer to Supported Value Types for the mapping between Python types and database columns.
##  Optimizing Queries with Prefetch 
By default, the AttributeHub manager prefetches all custom attributes when you query hubs. This means accessing `hub.get_attribute(...)` after a query does not trigger additional database queries:
    ```python
    from canvas_sdk.v1.data import AttributeHub
    # All custom attributes are prefetched automatically
    hubs = AttributeHub.objects.filter(type="meal_entry")
    for hub in hubs:
        # No additional queries — attributes are already loaded
        meal_type = hub.get_attribute("meal_type")
        calories = hub.get_attribute("calories")
    ```
###  Prefetching Specific Attributes 
When a hub has many attributes but you only need a few, use `with_only()` to prefetch only the attributes you need. This reduces the amount of data transferred from the database:
    ```python
    from canvas_sdk.v1.data import AttributeHub
    # Prefetch only the "calories" and "meal_type" attributes
    hubs = AttributeHub.objects.with_only(["calories", "meal_type"]).filter(type="meal_entry")
    for hub in hubs:
        calories = hub.get_attribute("calories")       # Loaded from prefetch cache
        meal_type = hub.get_attribute("meal_type")     # Loaded from prefetch cache
        notes = hub.get_attribute("notes")             # Falls back to a DB query (not prefetched)
    # Prefetch a single attribute
    hub = AttributeHub.objects.with_only("campaign_status").get(
        type="crm_sync", id="patient:abc123"
    )
    ```
If you access an attribute that was not included in `with_only()`, it will fall back to a database query. Use `with_only()` as an optimization, not a filter.
##  Use Case Example: CRM Campaign Sync 
Store synchronization state between a custom data model and an external CRM using AttributeHub:
    ```python
    from canvas_sdk.handlers.simple_api import SimpleAPI, api
    from canvas_sdk.effects.simple_api import JSONResponse
    from canvas_sdk.v1.data import AttributeHub, Patient
    from datetime import datetime
    class CRMSyncAPI(SimpleAPI):
        """API endpoint for syncing campaign data with external CRM."""
        PREFIX = "/crm"
        @api.post("/campaign/<campaign_id>/patient/<patient_id>")
        def sync_patient_campaign(self):
            campaign_id = self.request.path_params["campaign_id"]
            patient_id = self.request.path_params["patient_id"]
            patient = Patient.objects.get(id=patient_id)
            crm_data = self.request.json()
            # Store CRM sync state in AttributeHub
            hub, created = AttributeHub.objects.get_or_create(
                type="crm_campaign_sync",
                id=f"patient:{patient.id}:campaign:{campaign_id}"
            )
            hub.set_attributes({
                "crm_contact_id": crm_data.get("contact_id"),
                "campaign_status": crm_data.get("status"),
                "enrollment_date": crm_data.get("enrolled_at"),
                "last_synced": datetime.now(),
                "sync_direction": "crm_to_canvas"
            })
            return [JSONResponse({"status": "success", "hub_id": str(hub.id)})]
    ```
Later, retrieve the sync state when processing patient events:
    ```python
    from canvas_sdk.handlers.base import BaseHandler
    from canvas_sdk.events import EventType
    from canvas_sdk.v1.data import AttributeHub
    class CampaignEnrollmentHandler(BaseHandler):
        """Handler that checks CRM campaign sync state for patients."""
        RESPONDS_TO = EventType.Name(EventType.PATIENT_UPDATED)
        def compute(self):
            patient_id = self.target.id
            campaign_id = "wellness_2024"  # Your campaign identifier
            # Retrieve CRM sync state from AttributeHub
            hub, created = AttributeHub.objects.get_or_create(
                type="crm_campaign_sync",
                id=f"patient:{patient_id}:campaign:{campaign_id}"
            )
            if not created:
                crm_contact_id = hub.get_attribute("crm_contact_id")
                campaign_status = hub.get_attribute("campaign_status")
                last_synced = hub.get_attribute("last_synced")
                # Use the CRM data to drive clinical workflows
                if campaign_status == "enrolled":
                    # Patient is enrolled in CRM campaign - trigger relevant protocols
                    pass
            return []
    ```
##  Best Practices 
###  Data Organization 
  1. **Use descriptive type values** \- Choose meaningful type names that describe the purpose of the hub (e.g., "external_sync", "api_cache", "feature_flags")
  2. **Use consistent ID patterns** \- Use a consistent pattern for `id` (e.g., "entity_type:entity_id")
  3. **Namespace by purpose** \- Group related data under a single hub rather than creating multiple hubs for the same entity type
###  Data Privacy and Isolation 
  1. **Understand plugin data scoping** \- All AttributeHub data is isolated to your plugin's namespace
  2. **Implement proper authorization** \- Secure all APIs that expose AttributeHub data
  3. **Follow PHI guidelines** \- Treat all patient-related data with appropriate security measures
###  Performance 
  1. **Batch attribute updates** \- Use `set_attributes()` to set multiple values at once
  2. **Cache hub lookups** \- If accessing the same hub multiple times, store the reference
###  Data Integrity 
  1. **Use get_or_create** \- Use `get_or_create()` to avoid duplicate hubs
  2. **Handle None values** \- Always check if an attribute exists before using it
  3. **Validate data** \- Validate data before storing in AttributeHub
  4. **Clean up unused data** \- Remove AttributeHub instances that are no longer needed
###  Testing 
  1. **Use get_or_create in tests** \- This pattern works well for test isolation
  2. **Isolate test data** \- Create all data required by the test, within the test
##  See Also 
  - [Custom Data Overview](/sdk/custom-data/) \- Overview of all custom data techniques
  - [Design Considerations](/sdk/custom-data-design-considerations/) \- Choosing the right technique and avoiding anti-patterns
  - [CustomModels](/sdk/custom-data-custom-models/) \- Structured models with relationships
  - [Sharing Data](/sdk/custom-data-sharing-data/) \- Sharing data among plugins
  - [Testing Custom Data](/sdk/custom-data-testing/) \- Testing utilities and examples
  - [Data Models](/sdk/data/) \- Core SDK data models
  - [Caching API](/sdk/caching) \- Auto-expiring transient data
  - [Secrets](/sdk/secrets/) \- Managing API keys and sensitive configuration
----- END PAGE https://docs.canvasmedical.com/sdk/custom-data-attribute-hubs/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/custom-data-custom-models/
##  Overview 
CustomModels allow you to define fully structured, typed data models with relationships among entities and normalized data. Built on Django's ORM, CustomModels provide the most powerful and flexible approach to storing custom data in Canvas plugins.
The functionality expressed is a subset of the total ORM. The SDK omits some features in order to simplify the lifecycle of plugin installation and maintenance.
**Best for:**
  - Structured data with a stable, known schema
  - Relationships between entities (foreign keys, join tables)
  - Data requiring compound filtering, sorting, or aggregation
  - Data consumed by reports or analytics
**Example use cases:**
  - Provider specialties and certifications
  - Constructing new associations among Canvas SDK models
  - Custom workflows and forms
  - Integration-specific data structures
  - Practice-specific business entities
**Not ideal for** simple metadata on existing models, highly variable or schemaless data, or ephemeral data. Tables and columns cannot be dropped once created. See [Design Considerations](/sdk/custom-data-design-considerations/) for detailed guidance.
Custom models may be associated to core SDK data models by extending them with `ModelExtension`, or may be entirely standalone. As an example, a `StaffBiography` CustomModel could attach to a `CustomStaff(Staff, ModelExtension)` class, and be accessible via a `biography` property on `CustomStaff`.
Custom models must be defined within a `models` directory under the plugin top-level directory. E.g., `/my_plugin/models/custom_model_a.py`. If not, then database migrations will not be applied. (Extended SDK models may be defined anywhere since they do not require any database modifications.)
* * *
##  Basic CustomModel 
Create a custom model by extending `CustomModel`:
    ```python
    from canvas_sdk.v1.data.base import CustomModel
    from django.db.models import BooleanField, DateField, DateTimeField, DecimalField, IntegerField, JSONField, TextField 
    class HealthCoach(CustomModel):
        name = TextField()
        practicing_since = IntegerField()
        version = DecimalField(default=1.0, decimal_places=1, max_digits=3)
        is_accepting_patients = BooleanField()
        created_date = DateField(auto_now_add=True)
        last_modified_at = DateTimeField(auto_now_add=True)
        extended_attributes = JSONField()    
    ```
This above definition will result in a PostgreSQL table named `healthcoach`. It will have a primary key column named `dbid` of type `serial`, an auto-incrementing integer. It will have six additional columns of `text`, `integer`, `numeric(3,8)`, `boolean`, `jsonb`,`date`, and `timestamp with time zone`, respectively.
* * *
##  Schema Rules and Constraints 
To maintain safety on potentially large datasets, most constraints on CustomModels are not enforced within the database and must be enforced within plugin code.
Unsupported constraints:
  - `not null`
  - `max_length`
  - `references` (database-level foreign key constraints)
If applied to an existing dataset, these constraints could result in a full table rewrite operation, or prevent plugin installation. Note that while database-level `REFERENCES` constraints are not created, Django's ORM enforces `on_delete` behavior (`CASCADE`, `SET_NULL`, `DO_NOTHING`) at the application level — see Delete Behavior below.
Uniqueness constraints **are** supported via `UniqueConstraint` in `Meta.constraints`. See Uniqueness Constraints below.
###  Field Types 
The Canvas SDK provides Django-based field types for defining your models:
Field Type | Description | Supported Parameters  
---|---|---  
`TextField` | Variable-length text | `default`  
`IntegerField` | Integer values | `default`  
`DecimalField` | Decimal numbers | `default`, `max_digits`,`decimal_places`  
`BooleanField` | True/False values | `default`  
`DateField` | Date values | `auto_now`, `auto_now_add`, `default`  
`DateTimeField` | Date and time values | `auto_now`, `auto_now_add`, `default`  
`JSONField` | JSON-serializable data | `default`  
`ForeignKey` | Many-to-one relationship | `related_name`, `on_delete`, `to_field`  
`OneToOneField` | One-to-one relationship | `related_name`, `on_delete`, `to_field`, `primary_key`  
`ManyToManyField` | Many-to-many relationship | `through` (required), `related_name`  
If `default` is supplied it will be applied by the Django ORM, and will not be a PostgreSQL default. As a result, only new records will receive the value, and it will not cause a mass edit of existing records.
The `on_delete` parameter is required on `ForeignKey` and `OneToOneField`. It controls what happens to child records when the referenced parent record is deleted:
Value | Behavior  
---|---  
`CASCADE` | Automatically delete the child record when the parent is deleted.  
`SET_NULL` | Set the foreign key column to `NULL` when the parent is deleted. The child record is kept.  
`DO_NOTHING` | Take no action. The plugin is responsible for cleaning up or preventing orphaned references.  
These behaviors are enforced by Django's ORM at the application level. They apply when deleting via `model.delete()` or `queryset.delete()`, but not when using raw SQL.
###  Indexes 
Add indexes for frequently queried fields:
    ```python
    from canvas_sdk.v1.data.base import CustomModel
    from django.contrib.postgres.indexes import GinIndex
    from django.db.models import BooleanField, DateTimeField, Index, IntegerField, JSONField, TextField 
    class ProviderQualification(CustomModel):
        first_name = TextField()
        last_name = TextField()
        board_certified = BooleanField()
        practicing_since_year = IntegerField()
        extended_attributes = JSONField()
        created_at = DateTimeField()
        class Meta:
            indexes = [
                # Single-column index
                Index(fields=["practicing_since_year"]),
                # Composite index for common search combinations
                Index(fields=["first_name", "last_name"]),
                # Descending index for ordering records
                Index(fields=["-created_at"]),
                # Gin index for efficient JSON queries
                GinIndex(fields=["extended_attributes"])
            ]
    ```
**Index Best Practices:**
  - Index fields used in `filter()` and `order_by()`
  - Create composite indexes for common multi-field queries
  - **Do not** index `ForeignKey` or `OneToOneField` columns — they are indexed automatically. The SDK will raise an error if you declare a single-column index that duplicates an auto-indexed column.
###  Uniqueness Constraints 
Use `UniqueConstraint` in `Meta.constraints` to enforce uniqueness on one or more columns. Uniqueness is enforced at the database level via a `CREATE UNIQUE INDEX`.
    ```python
    from canvas_sdk.v1.data.base import CustomModel
    from django.db.models import TextField, UniqueConstraint
    class Specialty(CustomModel):
        name = TextField()
        code = TextField()
        class Meta:
            constraints = [
                UniqueConstraint(fields=["code"], name="uq_specialty_code"),
            ]
    ```
Composite uniqueness (multiple columns together must be unique):
    ```python
    from canvas_sdk.v1.data.base import CustomModel
    from django.db.models import DO_NOTHING, ForeignKey, TextField, UniqueConstraint
    from canvas_sdk.v1.data import Staff, ModelExtension
    class CustomStaff(Staff, ModelExtension):
        pass
    class StaffCertification(CustomModel):
        staff = ForeignKey(CustomStaff, to_field="dbid", on_delete=DO_NOTHING, related_name="%(app_label)s__certifications")
        certification_code = TextField()
        class Meta:
            constraints = [
                UniqueConstraint(fields=["staff", "certification_code"], name="uq_staff_cert"),
            ]
    ```
Each `UniqueConstraint` requires a `name` parameter — this is a standard Django requirement. Choose a descriptive name that won't collide with other constraints in your plugin.
Use Django field names (e.g., `"staff"`) rather than database column names (e.g., `"staff_id"`) in the `fields` list — the SDK resolves field names to column names automatically. The same applies to `Index` fields in `Meta.indexes`.
**Important:** Do not use `unique=True` on individual fields. The SDK will reject it with an error directing you to use `UniqueConstraint` instead. This is because `unique=True` modifies the column definition itself, and our DDL pipeline cannot retroactively alter existing columns — meaning a `unique=True` added after the initial deployment would silently have no effect.
**Constraint placement:** `UniqueConstraint` must be placed in `Meta.constraints`, not `Meta.indexes`. Although they are structurally similar to indexes, placing a `UniqueConstraint` in `Meta.indexes` would create a non-unique index. The SDK validates this and raises an error if it detects the mistake.
**Lifecycle:** Unique indexes are created with `CREATE UNIQUE INDEX IF NOT EXISTS`, making them safe to add at any time — they are applied idempotently on every deployment. However, if the table already contains duplicate values for the constrained columns, the index creation will fail. Clean up duplicates before adding the constraint.
Operation | Allowed | Explanation  
---|---|---  
Add UniqueConstraint | Yes | A unique index will be created if it does not already exist.  
Remove UniqueConstraint | No | Remove the constraint from your model and it will be ignored, but the index will remain in the database.  
* * *
##  Creating and Querying 
###  Creating Records 
    ```python
    from my_plugin.models import ProviderQualification
    # Create and save
    qualification = ProviderQualification(
        first_name="Jessica",
        last_name="Smith",
        board_certified=True,
        practicing_since_year=2005,
        extended_attributes={ "biography": "Lives in Fresno with her..." }
    )
    qualification.save()
    # Create in one step
    qualification = ProviderQualification.objects.create(
        first_name="Jessica",
        last_name="Smith",
        board_certified=True,
        practicing_since_year=2005,
        extended_attributes={ "biography": "Lives in Fresno with her..." }
    )
    # Get or create (avoids duplicates)
    qualification = ProviderQualification.objects.get_or_create(
        first_name="Jessica",
        last_name="Smith",
        defaults={
            "board_certified": True,
            "practicing_since_year": 2005,
            "extended_attributes": { "biography": "Lives in Fresno with her..." }
        }
    )
    ```
###  Querying Records 
    ```python
    from my_plugin.models import ProviderQualification
    from datetime import date
    # Get all records
    all_qualifications = ProviderQualification.objects.all()
    # Filter records
    board_certified = ProviderQualification.objects.filter(board_certified=True)
    # Get providers with 10+ years experience 
    experienced = ProviderQualification.objects.filter(
        practicing_since_year__lte=date.today().year - 10
    )
    # Get single record by database primary key
    try:
        jessica = ProviderQualification.objects.get(dbid=123)
    except ProviderQualification.DoesNotExist:
        jessica = None
    # Get single record by fields
    try:
        jessica = ProviderQualification.objects.get(first_name="Jessica", last_name="Smith")
    except ProviderQualification.DoesNotExist:
        jessica = None
    # Apply multiple filters
    senior_certified = ProviderQualification.objects.filter(
        board_certified=True,
        practicing_since_year__lte=2010  # Practicing since 2010 or earlier
    )
    # Order results
    by_experience = ProviderQualification.objects.order_by("practicing_since_year")
    # Limit results - get 5 most experienced (earliest practicing_since_year)
    top_five = ProviderQualification.objects.order_by("practicing_since_year")[:5]
    ```
###  Updating Records 
    ```python
    from my_plugin.models import ProviderQualification
    # Update single record
    qualification = ProviderQualification.objects.get(first_name="Jessica", last_name="Smith")
    qualification.practicing_since_year = 2004
    qualification.save()
    # Update multiple records
    ProviderQualification.objects.filter(
        board_certified=False
    ).update(board_certified=True)
    # Update or create
    qualification, created = ProviderQualification.objects.update_or_create(
        first_name="Michael",
        last_name="Johnson",
        defaults={
            "board_certified": True,
            "practicing_since_year": 2015,
            "extended_attributes": { "specialties": ["Cardiology", "Internal Medicine"] }
        }
    )
    ```
###  Deleting Records 
    ```python
    from my_plugin.models import ProviderQualification
    # Delete single record
    qualification = ProviderQualification.objects.get(first_name="Jessica", last_name="Smith")
    qualification.delete()
    # Delete multiple records - remove providers who started this year
    from datetime import date
    ProviderQualification.objects.filter(
        practicing_since_year=date.today().year
    ).delete()
    # Delete all records (use with caution!)
    ProviderQualification.objects.all().delete()
    ```
##  Extending the Canvas Data Model 
CustomModels may reference core SDK models by creating a proxy model with `ModelExtension`. This gives each plugin its own private handle on a shared SDK model, keeping `related_name` attributes isolated across plugins.
    ```python
    from canvas_sdk.v1.data import Staff, ModelExtension
    class CustomStaff(Staff, ModelExtension):
        pass
    ```
No new table is created — `CustomStaff` shares the `Staff` table and behaves identically for queries. Point your `ForeignKey` or `OneToOneField` at the proxy to get clean, un-namespaced reverse lookups.
For a full explanation of why proxy models exist, how `related_name` namespacing works, and how to reference SDK models directly without a proxy, see [Extending SDK Models](/sdk/custom-data-extending-sdk-models/).
* * *
##  One-to-One Relationships 
A one-to-one relationship links one record in a model to exactly one record in another model. Use `OneToOneField` to define this relationship.
###  Basic One-to-One 
    ```python
    from canvas_sdk.v1.data import Staff, ModelExtension
    from canvas_sdk.v1.data.base import CustomModel
    from django.db.models import CASCADE, DateTimeField, DecimalField, OneToOneField, TextField
    class CustomStaff(Staff, ModelExtension):
        """Extends Staff with custom attribute support."""
        pass
    class Biography(CustomModel):
        biography = TextField()
        language = TextField()
        version = DecimalField(default=1.0, decimal_places=1, max_digits=3)
        last_modified_at = DateTimeField(auto_now_add=True)
        staff = OneToOneField(
            CustomStaff, to_field="dbid", on_delete=CASCADE, related_name="biography"
        )
    ```
The above will create a table with a `serial` primary key, two `text` columns, a `numeric(1,3)` column, a `timestamptz` column, and an `integer` column named `staff_id` that contains a foreign key into the SDK `Staff` model. The `CustomStaff` class will contain the reverse mapping via `related_name`.
**Uniqueness:** A `OneToOneField` implies that the foreign key column is unique — each target record can be referenced by at most one row. The SDK automatically creates a `UNIQUE INDEX` on the foreign key column to enforce this at the database level. Do not add a separate `UniqueConstraint` for it — the SDK will raise an error if you declare a single-column `UniqueConstraint` or `Index` on an auto-indexed column.
###  One-to-One with `primary_key=True`
A `OneToOneField` can serve as the table's primary key by setting `primary_key=True`. This replaces the default auto-incrementing `dbid` column — the foreign key column becomes the sole primary key.
This pattern is useful when the child record has a strict 1:1 relationship with its parent and there is no need for a separate surrogate key.
    ```python
    from canvas_sdk.v1.data import Patient, ModelExtension
    from canvas_sdk.v1.data.base import CustomModel
    from django.db.models import CASCADE, JSONField, OneToOneField
    class CustomPatient(Patient, ModelExtension):
        pass
    class PatientPreferences(CustomModel):
        patient = OneToOneField(
            CustomPatient, to_field="dbid", on_delete=CASCADE,
            related_name="preferences", primary_key=True
        )
        preferences = JSONField(default=dict)
    ```
The above will create a table with a single `integer` primary key column `patient_id` (no `dbid` column) and a `jsonb` column. The primary key inherently enforces uniqueness, so no additional unique index is created.
**Note:** `primary_key=True` is only supported on `OneToOneField`. Setting it on a `ForeignKey` or any other field type will raise an error — use a `OneToOneField` instead when you need a shared primary key.
###  Creating One-to-One Records 
    ```python
    from my_plugin.models import CustomStaff, Biography
    # Get the staff member
    staff = CustomStaff.objects.get(id="staff-uuid")
    # Create biography
    biography = Biography.objects.create(
        staff=staff,
        biography="Dr. Smith is a board-certified cardiologist with over 20 years of experience...",
        language="English",
        version=1.0
    )
    ```
###  Querying One-to-One Relationships 
    ```python
    from my_plugin.models import CustomStaff, Biography
    # Access from biography to staff
    biography = Biography.objects.get(dbid=1)
    staff_member = biography.staff
    # Access from staff to biography (using related_name)
    staff = CustomStaff.objects.get(id="staff-uuid")
    try:
        bio = staff.biography
    except Biography.DoesNotExist:
        print("No biography found")
    # Find all staff with biographies in Spanish
    spanish_providers = CustomStaff.objects.filter(
        biography__language="Spanish"
    )
    # Find staff whose biography was last updated before a certain date
    from datetime import datetime, timedelta
    outdated_bios = CustomStaff.objects.filter(
        biography__last_modified_at__lte=datetime.now() - timedelta(days=365)
    )
    ```
* * *
##  One-to-Many Relationships 
A one-to-many (or many-to-one) relationship allows one record to be associated with multiple records in another model. Use `ForeignKey` to define this relationship.
###  Basic One-to-Many 
    ```python
    from canvas_sdk.v1.data import Staff, ModelExtension
    from canvas_sdk.v1.data.base import CustomModel
    from django.db.models import CASCADE, DateTimeField, DecimalField, ForeignKey, TextField
    class CustomStaff(Staff, ModelExtension):
      """Extends Staff with custom attribute support."""
      pass
    class Biography(CustomModel):
      biography = TextField()
      language = TextField()
      version = DecimalField(default=1.0, decimal_places=1, max_digits=3)
      last_modified_at = DateTimeField(auto_now_add=True)
      # Same as one-to-one, but a Foreign key with a plural 'related_name'. Now, each staff may have multiple biographies,
      # perhaps in different languages.
      staff = ForeignKey(
        CustomStaff, to_field="dbid", on_delete=CASCADE, related_name="biographies"
      )
    ```
###  Creating One-to-Many Records 
    ```python
    from my_plugin.models import CustomStaff, Biography
    # Get staff member
    staff = CustomStaff.objects.get(id="staff-uuid")
    # Create multiple biographies for one provider (e.g., in different languages)
    english_bio = Biography.objects.create(
        staff=staff,
        biography="Dr. Smith is a board-certified cardiologist with over 20 years of experience in interventional cardiology.",
        language="English",
        version=1.0
    )
    spanish_bio = Biography.objects.create(
        staff=staff,
        biography="La Dra. Smith es una cardióloga certificada con más de 20 años de experiencia en cardiología intervencionista.",
        language="Spanish",
        version=1.0
    )
    ```
###  Querying One-to-Many Relationships 
    ```python
    from my_plugin.models import CustomStaff, Biography
    # Access from biography to staff (forward)
    biography = Biography.objects.get(language="Spanish")
    provider = biography.staff
    print(f"Provider: {provider.first_name} {provider.last_name}")
    # Access from staff to biographies (reverse, using related_name)
    staff = CustomStaff.objects.get(id="staff-uuid")
    biographies = staff.biographies.all()
    for bio in biographies:
        print(f"- {bio.language}: {bio.biography[:50]}... (v{bio.version})")
    # Filter reverse relationship
    english_bios = staff.biographies.filter(language="English")
    # Query across relationship
    # Find all staff who have biographies in Spanish
    spanish_speaking_providers = CustomStaff.objects.filter(
        biographies__language="Spanish"
    )
    # Find staff with multiple biography versions
    from django.db.models import Count
    providers_with_multiple_bios = CustomStaff.objects.annotate(
        bio_count=Count('biographies')
    ).filter(bio_count__gt=1)
    # Count related records
    biography_count = staff.biographies.count()
    # Check existence
    has_spanish_bio = staff.biographies.filter(language="Spanish").exists()
    ```
* * *
##  Many-to-Many Relationships 
A many-to-many relationship allows multiple records in one model to be associated with multiple records in another model.
Many-to-many relationships require an **explicit through model** — a CustomModel that contains ForeignKey fields to both sides of the relationship. Standard Django allows `ManyToManyField` to create an implicit join table automatically, but the Canvas SDK does not support implicit through tables because each table must be a CustomModel with a managed schema lifecycle.
You can define the relationship in two ways:
  1. **Through model only** — Define the through model with ForeignKeys and query through it directly.
  2. **Through model +`ManyToManyField`** — Add a `ManyToManyField` with an explicit `through` parameter for cleaner ORM access.
Both approaches create the same database tables. The `ManyToManyField` adds ORM convenience (e.g., `specialty.staff.all()` instead of traversing the join table manually) but does not change the underlying schema.
###  Through Model Only 
The simplest approach is to define just the through model. This works well when the through model has additional metadata fields or when you prefer to query the join table directly.
    ```python
    from django.db.models import CASCADE, ForeignKey, Index, TextField, UniqueConstraint
    from canvas_sdk.v1.data.base import CustomModel
    from canvas_sdk.v1.data import Staff, ModelExtension
    class CustomStaff(Staff, ModelExtension):
      """Extends Staff with custom attribute support."""
      pass
    class Specialty(CustomModel):
      """Medical specialty (e.g., Cardiology, Neurology)."""
      name = TextField()
      class Meta:
        indexes = [
          Index(fields=["name"]),
        ]
    # Declaring this class will result in a join table called `staffspecialty`
    class StaffSpecialty(CustomModel):
      """Many-to-many relationship: Staff can have many specialties, specialties can have many staff."""
      staff = ForeignKey(
        CustomStaff,
        to_field="dbid",
        on_delete=CASCADE,
        related_name="staff_specialties"
      )
      specialty = ForeignKey(
        Specialty,
        to_field="dbid",
        on_delete=CASCADE,
        related_name="staff_specialties"
      )
      class Meta:
        constraints = [
          UniqueConstraint(
            fields=["staff", "specialty"],
            name="unique_staff_specialty",
          ),
        ]
    ```
This creates a many-to-many relationship where:
  - One staff member can have multiple specialties
  - One specialty can be assigned to multiple staff members
  - `StaffSpecialty` is the through model that connects them
**Preventing duplicate associations:** Through models typically need a uniqueness constraint on the pair of foreign key columns to prevent the same association from being created twice. Add a `UniqueConstraint` to the through model's `Meta.constraints` referencing both FK field names (e.g., `staff` and `specialty`). Without this, calling `StaffSpecialty.objects.create(staff=staff, specialty=cardiology)` twice would create two identical rows. See Uniqueness Constraints for more details on constraint naming and lifecycle.
###  Through Model + ManyToManyField 
Adding a `ManyToManyField` with an explicit `through` parameter gives you direct ORM access to the related objects without manually traversing the join table.
**Important:** The `through` parameter is **required**. A `ManyToManyField` without `through` will cause an error because the SDK cannot manage implicit join tables.
    ```python
    from django.db.models import CASCADE, ForeignKey, Index, ManyToManyField, TextField, UniqueConstraint
    from canvas_sdk.v1.data.base import CustomModel
    from canvas_sdk.v1.data import Staff, ModelExtension
    class CustomStaff(Staff, ModelExtension):
      """Extends Staff with custom attribute support."""
      pass
    class Specialty(CustomModel):
      """Medical specialty (e.g., Cardiology, Neurology)."""
      name = TextField()
      staff = ManyToManyField(
        CustomStaff,
        through="StaffSpecialty",
        related_name="%(app_label)s_specialties",
      )
      class Meta:
        indexes = [
          Index(fields=["name"]),
        ]
    class StaffSpecialty(CustomModel):
      """Through model for the staff-specialty relationship."""
      staff = ForeignKey(
        CustomStaff,
        to_field="dbid",
        on_delete=CASCADE,
        related_name="%(app_label)s_staff_specialties",
      )
      specialty = ForeignKey(
        Specialty,
        to_field="dbid",
        on_delete=CASCADE,
        related_name="staff_specialties",
      )
      class Meta:
        constraints = [
          UniqueConstraint(
            fields=["staff", "specialty"],
            name="unique_staff_specialty",
          ),
        ]
    ```
With the `ManyToManyField` declared, you can traverse the relationship directly:
    ```python
    # Direct access to related objects (returns Staff queryset, not StaffSpecialty)
    specialty = Specialty.objects.get(name="Cardiology")
    staff_members = specialty.staff.all()
    # Reverse access from staff to specialties
    staff = CustomStaff.objects.get(id="staff-uuid")
    specialties = staff.staff_plus_specialties.all()  # uses the ManyToManyField's related_name
    ```
Compare this with the through-model-only approach, where you must navigate through the join table:
    ```python
    # Without ManyToManyField — must traverse the join table
    staff_members = [ss.staff for ss in specialty.staff_specialties.all()]
    ```
####  Differences from Standard Django ManyToManyField 
Behavior | Standard Django | Canvas SDK  
---|---|---  
`through` parameter | Optional — Django creates an implicit join table | **Required** — must reference a CustomModel  
`.add()`, `.remove()`, `.set()` | Available when no explicit through model | **Not available** — use the through model's `.objects.create()` and `.delete()` instead  
`.clear()` | Available | **Not available** — use `StaffSpecialty.objects.filter(...).delete()` instead  
`.all()`, filtering, `prefetch_related` | Available | Available  
Because Django requires you to use the through model directly for creating and deleting relationships when an explicit `through` is declared, the CRUD patterns are the same whether or not you add the `ManyToManyField`. The field's value is in query convenience — direct `.all()` access and cleaner `prefetch_related` lookups.
####  related_name with ManyToManyField 
When a `ManyToManyField` targets a core SDK model (like `Staff` or `Patient`), you **must** use the `%(app_label)s_` prefix in `related_name` to avoid naming collisions between plugins:
    ```python
    staff = ManyToManyField(
        CustomStaff,
        through="StaffSpecialty",
        related_name="%(app_label)s_specialties",  # becomes e.g. "my_plugin_specialties"
    )
    ```
This is the same namespacing requirement that applies to `ForeignKey` and `OneToOneField` when targeting SDK models. See [Extending SDK Models](/sdk/custom-data-extending-sdk-models/) for a full explanation of when namespacing is required and how proxy models avoid it.
###  Creating Many-to-Many Records 
Regardless of whether you use `ManyToManyField`, create and delete relationships through the through model directly:
    ```python
    from my_plugin.models import CustomStaff, Specialty, StaffSpecialty
    # Create specialties
    cardiology = Specialty.objects.create(name="Cardiology")
    internal_medicine = Specialty.objects.create(name="Internal Medicine")
    emergency_medicine = Specialty.objects.create(name="Emergency Medicine")
    # Get staff member
    staff = CustomStaff.objects.get(id="staff-uuid")
    # Create associations between staff and specialties
    StaffSpecialty.objects.create(staff=staff, specialty=cardiology)
    StaffSpecialty.objects.create(staff=staff, specialty=internal_medicine)
    # Bulk create multiple associations at once
    specialties_to_add = [emergency_medicine, cardiology]
    staff_specialties = [
        StaffSpecialty(staff=staff, specialty=specialty) for specialty in specialties_to_add
    ]
    StaffSpecialty.objects.bulk_create(staff_specialties)
    # Replace all specialties for a staff member
    # First, remove existing associations
    StaffSpecialty.objects.filter(staff=staff).delete()
    # Then create new associations
    new_specialties = [cardiology, emergency_medicine]
    new_staff_specialties = [
        StaffSpecialty(staff=staff, specialty=specialty) for specialty in new_specialties
    ]
    StaffSpecialty.objects.bulk_create(new_staff_specialties)
    ```
**Note:** Do not use `.add()`, `.remove()`, `.set()`, or `.clear()` on the `ManyToManyField`. Django disables these methods when an explicit `through` model is declared. Use the through model's manager (e.g., `StaffSpecialty.objects`) for all create and delete operations.
###  Querying Many-to-Many Relationships 
    ```python
    from my_plugin.models import CustomStaff, Specialty, StaffSpecialty
    # Access staff member's specialties through the join table
    staff = CustomStaff.objects.get(id="staff-uuid")
    staff_specialty_records = staff.staff_specialties.all()
    for staff_specialty in staff_specialty_records:
        print(f"- {staff_specialty.specialty.name}")
    # Get just the specialty names
    specialty_names = [ss.specialty.name for ss in staff.staff_specialties.all()]
    # Access all staff members with a specific specialty (reverse)
    cardiology = Specialty.objects.get(name="Cardiology")
    cardiology_staff_records = cardiology.staff_specialties.all()
    for staff_specialty in cardiology_staff_records:
        staff_member = staff_specialty.staff
        print(f"- {staff_member.first_name} {staff_member.last_name}")
    # Find staff IDs with specific specialties
    staff_ids = StaffSpecialty.objects.filter(
        specialty__name__in=["Cardiology", "Internal Medicine"]
    ).values_list("staff_id", flat=True)
    # Find staff members with a specific specialty
    cardiologists = CustomStaff.objects.filter(
        staff_specialties__specialty__name="Cardiology"
    ).distinct()
    # Check if a staff member has a specific specialty
    has_cardiology = staff.staff_specialties.filter(specialty__name="Cardiology").exists()
    # Count specialties for a staff member
    specialty_count = staff.staff_specialties.count()
    # Efficient querying with prefetch_related
    staff_with_specialties = (
        CustomStaff.objects
        .prefetch_related("staff_specialties__specialty")
        .all()
    )
    for staff in staff_with_specialties:
        specialties = [ss.specialty.name for ss in staff.staff_specialties.all()]
        print(f"{staff.first_name} {staff.last_name}: {', '.join(specialties)}")
    ```
**Key points about many-to-many relationships:**
  - Both sides of the relationship can access the through model using `related_name`
  - Without `ManyToManyField`: `staff.staff_specialties.all()` returns `StaffSpecialty` objects — access the related object via `ss.specialty`
  - With `ManyToManyField`: `specialty.staff.all()` returns `Staff` objects directly
  - You can add additional fields to the through model to store metadata about the relationship (e.g., date assigned, certification level, etc.)
  - Query across the relationship using double underscores: `CustomStaff.objects.filter(staff_specialties__specialty__name="Cardiology")`
##  Delete Behavior 
The `on_delete` parameter on `ForeignKey` and `OneToOneField` controls what happens to child records when a parent record is deleted. The SDK supports three values:
  - **`CASCADE`** — Delete the child record automatically. This is the most common choice for tightly-coupled relationships like join table entries, child records that have no meaning without their parent, or `OneToOneField` with `primary_key=True`.
  - **`SET_NULL`** — Set the foreign key column to `NULL`, keeping the child record. Useful when the child has independent value even if its parent is removed (e.g., an audit log entry whose associated staff member has been deactivated).
  - **`DO_NOTHING`** — Take no automatic action. The plugin is fully responsible for preventing orphaned references.
These behaviors are enforced at the Django ORM level, not by database-level foreign key constraints. They apply when deleting via `model.delete()` or `queryset.delete()`.
**Tip:** Use `CASCADE` on through-model (join table) foreign keys so that deleting either side of a many-to-many relationship automatically cleans up the association rows.
##  The CustomModel Lifecycle 
Managing database schemas necessarily introduces complexity, because there is state to maintain over time as the software evolves. Common pitfalls include expensive table rewrite operations, migrations that fail in some environments due to manual changes, database system-specific nuances, unsatisfied foreign key constraints due to data corruption or improper order of operations, etc.
The Canvas SDK Custom Data feature aims to simplify maintenance, while sacrificing some rigor found in a full migration system like Django's.
Operation | Allowed | Explanation  
---|---|---  
Create Model | Yes | A table corresponding to your CustomModel will be created if it does not exist. An autoincrementing column named `dbid` will be its sole attribute.  
Add Field | Yes | A column corresponding to a Field declared within your CustomModel will be added to the table if it does not exist. It will be nullable, without defaults to eliminate table rewrites.  
Add UniqueConstraint | Yes | A unique index will be created if it does not already exist. Fails if existing data contains duplicates for the constrained columns.  
Add Index | Yes | An index will be created if it does not already exist.  
Alter Field | No | This can cause a table rewrite, and requires a full migration metadata system. Create a new Field in your model. Copy data from old to new.  
Drop Field | No | This will cause a table rewrite, and requires a full migration metadata system. Remove the Field from your model and it will be ignored.  
Drop UniqueConstraint | No | Remove the constraint from your model and it will be ignored, but the unique index will remain in the database.  
Drop Index | No | Remove the index from your model and it will be ignored, but the index will remain in the database.  
Alter Model | No | Requires a full migration metadata system. Create a new Model in your plugin. Copy data from old to new.  
Drop Model | No | Requires a full migration metadata system. Remove the model from your plugin and it will be ignored.  
###  Best Practices 
  1. Emphasize local development over use of a development EMR instance.
  2. Write [automated tests](/sdk/custom-data-testing/) exercising your business logic.
  3. Extract business logic and CRUD operations into "service" classes that can be tested in isolation.
##  Advanced Patterns 
###  Combining Approaches 
You can combine CustomModels with [AttributeHubs](/sdk/custom-data-attribute-hubs/) for maximum flexibility:
    ```python
    from canvas_sdk.v1.data.base import CustomModel
    from canvas_sdk.v1.data import AttributeHub, Staff, ModelExtension
    from django.db.models import CASCADE, ForeignKey, SET_NULL, TextField
    class CustomStaff(Staff, ModelExtension):
        pass
    class Department(CustomModel):
        """Structured department model."""
        name = TextField()
        code = TextField()
    class StaffDepartment(CustomModel):
        """Staff can belong to multiple departments."""
        staff = ForeignKey(
            CustomStaff, on_delete=SET_NULL, related_name="department_assignments"
        )
        department = ForeignKey(
            Department, on_delete=CASCADE, related_name="staff_members"
        )
        role = TextField()
    # Use CustomModels for structured data with relationships
    staff = CustomStaff.objects.get(id="staff-uuid")
    dept = Department.objects.get(code="CARDIO")
    StaffDepartment.objects.create(
        staff=staff,
        department=dept,
        role="Lead Physician"
    )
    # Use an AttributeHub for flexible, unstructured data
    hub, created = AttributeHub.objects.get_or_create(
        type="staff_preferences",
        id=f"staff:{staff.id}"
    )
    hub.set_attributes({
        "pager_number": "555-1234",
        "preferred_contact": "email",
        "office_hours": {"monday": "9-5", "tuesday": "9-5"}
    })
    ```
###  Query Optimization 
Optimize database queries using `select_related` and `prefetch_related`:
    ```python
    from my_plugin.models import Specialty, StaffSpecialty, CustomStaff
    # Use select_related for ForeignKey (SQL JOIN)
    # Load StaffSpecialty with related staff and specialty in one query
    staff_specialties = StaffSpecialty.objects.select_related("staff", "specialty").all()
    for ss in staff_specialties:
        # No additional queries - both staff and specialty are already loaded
        print(f"{ss.staff.first_name} {ss.staff.last_name}: {ss.specialty.name}")
    # Use prefetch_related for reverse ForeignKey relationships
    # Load staff with all their specialties efficiently
    staff_list = CustomStaff.objects.prefetch_related("staff_specialties__specialty").all()
    for staff in staff_list:
        # No additional queries - staff_specialties and specialties are already loaded
        for ss in staff.staff_specialties.all():
            print(f"{staff.first_name}: {ss.specialty.name}")
    # Prefetch specialties for multiple staff members
    specialties_list = Specialty.objects.prefetch_related("staff_specialties__staff").all()
    for specialty in specialties_list:
        staff_members = [ss.staff for ss in specialty.staff_specialties.all()]
        print(f"{specialty.name}: {len(staff_members)} staff members")
    # Use Prefetch for custom filtering
    from django.db.models import Prefetch
    # Only load staff specialties with specific specialty names
    staff_with_filtered_specialties = CustomStaff.objects.prefetch_related(
        Prefetch(
            "staff_specialties",
            queryset=StaffSpecialty.objects.filter(
                specialty__name__in=["Cardiology", "Neurology"]
            ).select_related("specialty")
        )
    ).all()
    ```
###  Complex Queries 
Use Django's Q objects for complex filtering and aggregation:
    ```python
    from django.db.models import Q, Count
    from my_plugin.models import CustomStaff, Specialty, StaffSpecialty
    # OR conditions - Find staff with Cardiology OR Neurology specialty
    staff_with_cardio_or_neuro = CustomStaff.objects.filter(
        Q(staff_specialties__specialty__name="Cardiology") |
        Q(staff_specialties__specialty__name="Neurology")
    ).distinct()
    # AND conditions - Find specialties with "Cardiology" or "Medicine" in the name
    cardio_or_medicine = Specialty.objects.filter(
        Q(name__icontains="Cardiology") | Q(name__icontains="Medicine")
    )
    # Negation - Find staff WITHOUT a specific specialty
    staff_without_cardiology = CustomStaff.objects.exclude(
        staff_specialties__specialty__name="Cardiology"
    )
    # Complex filtering - Staff with multiple specific specialties
    # Note: This requires DISTINCT because joins can create duplicate rows
    staff_with_multiple = CustomStaff.objects.filter(
        staff_specialties__specialty__name="Cardiology"
    ).filter(
        staff_specialties__specialty__name="Internal Medicine"
    ).distinct()
    # Count related objects - Staff with specialty counts
    staff_with_counts = CustomStaff.objects.annotate(
        specialty_count=Count("staff_specialties")
    ).filter(specialty_count__gte=2)
    # Group by and aggregate - Count how many staff have each specialty
    specialty_counts = Specialty.objects.annotate(
        staff_count=Count("staff_specialties")
    ).order_by("-staff_count")
    for specialty in specialty_counts:
        print(f"{specialty.name}: {specialty.staff_count} staff members")
    ```
##  Best Practices 
###  Model Design 
  1. **Use appropriate field types** \- Choose the most specific field type for your data
  2. **Define related_name** \- Always specify `related_name` for clear reverse relationships
  3. **Keep models focused** \- Each model should represent a single, well-defined concept
###  Relationships 
  1. **Choose the right relationship type** \- OneToOne for 1:1, ForeignKey for 1:many, join tables and "through" models for many:many
  2. **Use through models** \- To create a join table bridging two other entities, create a CustomModel representing the relationship
  3. **Handle deletions** \- Use `CASCADE` on join table foreign keys so associations are cleaned up automatically. Use `SET_NULL` when child records should survive parent deletion. Use `DO_NOTHING` only when you manage cleanup explicitly in plugin code
###  Performance 
  1. **Add indexes strategically** \- Index frequently filtered fields - foreign key fields are automatically indexed
  2. **Use select_related** \- For ForeignKey and OneToOneField to reduce queries
  3. **Use prefetch_related** \- For reverse ForeignKey fields (including join tables for many-to-many fields)
  4. **Avoid N+1 queries** \- Always prefetch related data when iterating
  5. **Use exists() for checks** \- More efficient than count() or len()
  6. **Use iterator() for large datasets** \- Reduces memory usage for processing many records
###  Data Integrity 
  1. **Enforce uniqueness with UniqueConstraint** \- Use `UniqueConstraint` in `Meta.constraints` to prevent duplicate data at the database level
  2. **Validate in model methods** \- Add custom validation in `clean()` method
  3. **Use transactions** \- Wrap multiple operations in atomic transactions
  4. **Handle DoesNotExist** \- Always catch exceptions when using `get()`
###  Testing 
  1. **Use model factories** \- Create test data with factory patterns
  2. **Test model methods** \- Verify custom model methods and properties
  3. **Test relationships** \- Ensure relationships work in both directions
  4. **Test data quality** \- The plugin is responsible for ensuring uniqueness and validity of foreign keys
  5. **Test edge cases** \- Test with null values, empty strings, boundary conditions
##  See Also 
  - [Custom Data Overview](/sdk/custom-data/) \- Overview of all custom data techniques
  - [Extending SDK Models](/sdk/custom-data-extending-sdk-models/) \- Proxy models, `related_name` namespacing, and referencing SDK models
  - [Design Considerations](/sdk/custom-data-design-considerations/) \- Choosing the right technique and avoiding anti-patterns
  - [AttributeHubs](/sdk/custom-data-attribute-hubs/) \- Standalone key-value storage
  - [Transactions](/sdk/custom-data-transactions/) \- All-or-nothing writes with `transaction.atomic()`
  - [Sharing Data](/sdk/custom-data-sharing-data/) \- Sharing data among plugins
  - [Testing Custom Data](/sdk/custom-data-testing/) \- Testing utilities and examples
  - [Data Models](/sdk/data/) \- Core SDK data models
  - [Caching API](/sdk/caching) \- Auto-expiring transient data
----- END PAGE https://docs.canvasmedical.com/sdk/custom-data-custom-models/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/custom-data-design-considerations/
Choosing the right storage technique prevents performance problems, data inconsistencies, and unnecessary code complexity down the road. This page describes common anti-patterns for each technique and recommends alternatives.
For an overview of available techniques, see the [Custom Data Overview](/sdk/custom-data/).
##  Extending SDK Models with Custom Data 
To attach custom fields to existing SDK models (Patient, Staff, etc.), use a [CustomModel](/sdk/custom-data-custom-models/) with a `OneToOneField` pointing at the SDK model. This gives you typed, indexed columns with full ORM support — `select_related`, reverse lookups via `related_name`, and compound filtering in a single query.
    ```python
    from canvas_sdk.v1.data import Patient, ModelExtension
    from canvas_sdk.v1.data.base import CustomModel
    from django.db.models import BooleanField, DO_NOTHING, IntegerField, OneToOneField, TextField
    class CustomPatient(Patient, ModelExtension):
        pass
    class PatientProfile(CustomModel):
        patient = OneToOneField(
            CustomPatient, to_field="dbid", on_delete=DO_NOTHING,
            related_name="profile"
        )
        preferred_language = TextField()
        risk_score = IntegerField()
        is_vip = BooleanField()
    ```
CustomModels with `OneToOneField` are preferred because they offer typed columns, indexing, compound queries, and a schema that is visible and self-documenting. See [Extending SDK Models](/sdk/custom-data-extending-sdk-models/) for details on proxy models and `related_name` namespacing.
For truly simple, one-off metadata that doesn't justify a table (e.g., a single configuration flag), an [AttributeHub](/sdk/custom-data-attribute-hubs/) can be a lighter-weight alternative.
##  AttributeHubs — When to Reconsider 
AttributeHubs use EAV (entity-attribute-value) storage and are standalone — not attached to any Canvas model. They are convenient for one-off state and configuration, but the same EAV limitations apply when used at scale. Their best application is storing a collection of attributes that will mainly be retrieved by identifier rather than by value.
###  Modeling entities with relationships 
If you have "departments" and need to assign staff to them, encoding `staff_id` as a string attribute means no JOINs, no referential integrity, potentially duplicated data. The plugin must manually maintain consistency.
**Use instead:** [CustomModels](/sdk/custom-data-custom-models/) with `ForeignKey` fields and junction tables handle relationships naturally, with ORM-level traversal and `prefetch_related` support.
###  Large homogeneous collections 
Storing thousands of hubs of `type="patient_visit"` where you need to filter, sort, or paginate across them becomes expensive. Each filter condition requires a JOIN to the attribute table.
**Use instead:** A [CustomModel](/sdk/custom-data-custom-models/) with typed, indexed columns. Filtering, sorting, and pagination use standard SQL operations.
###  Data requiring aggregation 
Trying to SUM, AVG, or COUNT across AttributeHub attributes requires joining to the attribute table and selecting the correct typed column (`int_value`, `decimal_value`, etc.) per attribute name. This is fragile and slow.
**Use instead:** [CustomModel](/sdk/custom-data-custom-models/) columns make Django ORM aggregation (`annotate`, `aggregate`) straightforward.
###  Data with a consistent schema 
If every hub of a given `type` has the same set of attributes, you've designed a schema — just without enforcement or indexes. You're paying the cost of EAV without the benefit of flexibility.
**Use instead:** A [CustomModel](/sdk/custom-data-custom-models/) gives you type safety, column-level indexes, and cleaner queries.
##  CustomModels — When to Reconsider 
CustomModels create real database tables with typed columns. They are the most powerful option but carry a commitment: tables can be added but never dropped via the SDK, and fields can be added but never altered or removed.
###  Simple metadata on existing models 
For a small number of independent metadata fields on an SDK model (e.g., a single `is_vip` flag on Patient), a full CustomModel with `OneToOneField` is the recommended approach — it gives you typed columns, indexing, and compound queries. However, if the overhead of a table feels excessive for truly one-off data, an [AttributeHub](/sdk/custom-data-attribute-hubs/) keyed by entity type and ID can serve as a lightweight alternative.
###  Highly dynamic or schemaless data 
If every record has different fields — for example, caching responses from external APIs where the payload varies per endpoint — a CustomModel forces a rigid schema. You'll accumulate nullable columns for each variation, and fields can never be dropped.
**Use instead:** [AttributeHubs](/sdk/custom-data-attribute-hubs/) for truly schemaless data, or a CustomModel with a single `JSONField` if you still want a table but need flexible contents.
###  Ephemeral data 
CustomModel tables are permanent. Once created, they cannot be dropped via the SDK. For short-lived data like session tokens, rate-limit windows, or temporary processing state, a persistent table is the wrong tool.
**Use instead:** The [Caching API](/sdk/caching) for data with a natural TTL. For semi-persistent unstructured state, [AttributeHubs](/sdk/custom-data-attribute-hubs/) are lighter weight.
###  Premature normalization 
Don't create five interrelated CustomModels with foreign keys when the data is simple and queried infrequently. Over-engineering the schema early is costly because tables cannot be dropped if you change your mind.
**Use instead:** Start with fewer models. A single `JSONField` column or an [AttributeHub](/sdk/custom-data-attribute-hubs/) can hold loosely structured data until access patterns stabilize and justify a richer schema.
##  Quick Reference 
Situation | Recommended Approach  
---|---  
Custom fields on Patient, Staff, or other SDK models | CustomModel with `OneToOneField`  
Provider preferences (notification settings, display options) | CustomModel with `OneToOneField`  
API sync cursors, external system state | AttributeHub  
Plugin configuration or feature flags | AttributeHub  
One-off key-value data unrelated to a Canvas model | AttributeHub  
Rapid prototyping before committing to a schema | AttributeHub  
Structured entities with a stable, known schema | CustomModel  
Relationships between entities (foreign keys, join tables) | CustomModel  
Data requiring compound filtering, sorting, or aggregation | CustomModel  
Data consumed by reports or analytics | CustomModel  
High-write-frequency counters or accumulators | CustomModel  
Short-lived data that should auto-expire | [Caching API](/sdk/caching)  
##  See Also 
  - [Custom Data Overview](/sdk/custom-data/) \- Introduction to custom data storage
  - [Extending SDK Models](/sdk/custom-data-extending-sdk-models/) \- Proxy models and referencing SDK models
  - [AttributeHubs](/sdk/custom-data-attribute-hubs/) \- Standalone key-value storage
  - [CustomModels](/sdk/custom-data-custom-models/) \- Django models for structured data
  - [Transactions](/sdk/custom-data-transactions/) \- All-or-nothing writes with `transaction.atomic()`
  - [Caching API](/sdk/caching) \- Auto-expiring transient data
----- END PAGE https://docs.canvasmedical.com/sdk/custom-data-design-considerations/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/custom-data-extending-sdk-models/
##  Overview 
SDK models like `Patient`, `Staff`, and `Note` are shared across all plugins. To attach custom data or create relationships to these models from your [CustomModels](/sdk/custom-data-custom-models/), extend them with `ModelExtension` to create a plugin-private proxy model.
* * *
##  Creating a Model Extension 
Subclass the SDK model together with `ModelExtension`:
    ```python
    from canvas_sdk.v1.data import Staff, ModelExtension
    class CustomStaff(Staff, ModelExtension):
        pass
    ```
What happens automatically:
  - **`proxy = True`** is set by the `ModelExtensionMetaClass`. No new database table is created — the proxy shares the parent model's table.
  - **`app_label`** is set to your plugin name (derived from the module path).
  - `CustomStaff` behaves identically to `Staff` for queries — `CustomStaff.objects.all()` returns the same rows as `Staff.objects.all()`.
You can name the class anything, but it **must** subclass both a concrete SDK model and `ModelExtension`.
Extended SDK models may be defined anywhere in your plugin since they do not require database modifications. However, placing them in the `models` directory alongside your CustomModels is recommended for clarity.
* * *
##  Why Proxy Models? 
SDK models are shared across every plugin in the system. If two plugins each added a bare `related_name="biography"` on a `ForeignKey` pointing at `Staff`, Django would raise a clash error — both reverse relations would compete for the same attribute on the shared `Staff` class.
Proxy models solve this by giving each plugin its own private subclass of the SDK model. Because `CustomStaff` is a distinct model (even though it shares the same table), reverse relations registered on `CustomStaff` are scoped to the plugin that defined it. The shared SDK model stays clean and unaffected.
* * *
##  Referencing SDK Models from CustomModels 
When a CustomModel needs a `ForeignKey` or `OneToOneField` pointing at an SDK model, you have two approaches.
###  Approach 1: Via Proxy (Recommended) 
Create a `ModelExtension` proxy and point your relationship field at it. Because the target is plugin-private, `related_name` can be any simple name — no namespacing required.
    ```python
    from canvas_sdk.v1.data import Staff, ModelExtension
    from canvas_sdk.v1.data.base import CustomModel
    from django.db.models import DO_NOTHING, OneToOneField, TextField
    class CustomStaff(Staff, ModelExtension):
        pass
    class Biography(CustomModel):
        staff = OneToOneField(
            CustomStaff, to_field="dbid", on_delete=DO_NOTHING,
            related_name="biography"
        )
        text = TextField()
    ```
Reverse lookup works through the proxy:
    ```python
    staff = CustomStaff.objects.get(id="some-uuid")
    bio = staff.biography  # accesses the Biography via related_name
    ```
###  Approach 2: Direct SDK Model with Namespaced `related_name`
Point directly at the SDK model, but you **must** namespace the `related_name` to prevent collisions across plugins. Two formats are accepted:
Format | Example | Notes  
---|---|---  
`%(app_label)s_` prefix (recommended) | `related_name="%(app_label)s_biography"` | Django substitutes your plugin's `app_label` at class creation time  
Hardcoded plugin prefix | `related_name="my_plugin_biography"` | Works, but breaks if you rename the plugin  
`"+"` | `related_name="+"` | Disables the reverse relation entirely  
    ```python
    from canvas_sdk.v1.data import Staff
    from canvas_sdk.v1.data.base import CustomModel
    from django.db.models import DO_NOTHING, OneToOneField, TextField
    class Biography(CustomModel):
        staff = OneToOneField(
            Staff, to_field="dbid", on_delete=DO_NOTHING,
            related_name="%(app_label)s_biography"
        )
        text = TextField()
    ```
###  Comparison 
| Via Proxy | Direct SDK Model  
---|---|---  
`related_name` namespacing required? | No | Yes  
Reverse lookup available? | Yes, via the proxy class | Yes, via the SDK model  
Reverse attribute name | Simple (e.g., `staff.biography`) | Prefixed (e.g., `staff.my_plugin_biography`)  
Extra class needed? | Yes (`ModelExtension` proxy) | No  
In most cases, Approach 1 is preferred — it keeps `related_name` values short and readable, and the proxy class is reusable across multiple CustomModels in the same plugin.
* * *
##  Proxying Related Objects with `proxy_field`
When you use `ModelExtension` proxies and follow related objects through ForeignKey fields, the returned instance is the base SDK class — not your proxy. For example:
    ```python
    from canvas_sdk.v1.data import Note, Patient, ModelExtension
    class CustomPatient(Patient, ModelExtension):
        def full_display_name(self):
            # custom method only available on CustomPatient
            return f"{self.first_name} {self.last_name} (DOB: {self.birth_date})"
    class CustomNote(Note, ModelExtension):
        pass
    note = CustomNote.objects.get(id="89992c23-c298-4118-864a-26cb3e1ae822")
    note.patient                       # returns a Patient, not CustomPatient
    note.patient.full_display_name()   # AttributeError!
    ```
This happens because Django's ForeignKey descriptor resolves the relation to the concrete model (`Patient`), unaware of your proxy class. You would need an extra query to "re-fetch" the object as a `CustomPatient`.
###  The `proxy_field` descriptor 
`proxy_field` solves this by intercepting the ForeignKey access and transparently returning the proxy class instead:
    ```python
    from canvas_sdk.v1.data import Note, Patient, ModelExtension, proxy_field
    class CustomPatient(Patient, ModelExtension):
        def full_display_name(self):
            return f"{self.first_name} {self.last_name} (DOB: {self.birth_date})"
    class CustomNote(Note, ModelExtension):
        patient = proxy_field(CustomPatient)
    note = CustomNote.objects.get(id="89992c23-c298-4118-864a-26cb3e1ae822")
    note.patient                       # returns a CustomPatient instance
    note.patient.full_display_name()   # works!
    ```
No extra queries are issued — `proxy_field` reuses the already-fetched row and swaps its Python class to the proxy. Because proxy models share the same database table, this is safe and efficient.
###  How it works 
`proxy_field` is a Python [descriptor](https://docs.python.org/3/howto/descriptor.html). When you declare `patient = proxy_field(CustomPatient)` on a model class:
  1. `__set_name__` runs at class creation time and finds the original FK descriptor (`patient`) from the parent class in the MRO.
  2. `__get__` delegates to that original descriptor to load the related object, then sets `__class__` on the result to your proxy class.
  3. `__set__` passes assignment through to the original descriptor, so `note.patient = some_patient` continues to work normally.
  4. Accessing the attribute on the class (e.g., `CustomNote.patient`) returns the descriptor itself, not a model instance.
###  When to use `proxy_field`
Use `proxy_field` when:
  - You have `ModelExtension` proxies for multiple SDK models and need to navigate between them while keeping access to your custom methods or `related_name` fields.
  - You want to avoid extra database queries to "re-fetch" a related object as the proxy type.
`proxy_field` is not needed when:
  - You don't add custom methods, properties or `related_name` fields to your proxy class.
  - You access the related object's fields directly (e.g., `note.patient.first_name`) without needing proxy-specific behavior.
###  Null foreign keys 
`proxy_field` handles nullable ForeignKeys safely — if the relation is `None`, it returns `None` without error:
    ```python
    note = CustomNote.objects.get(id="89992c23-c298-4118-864a-26cb3e1ae822")
    note.patient  # returns None if the FK is null, not an error
    ```
* * *
##  Common Errors 
###  `ValueError`: non-namespaced `related_name` on SDK model target 
If you point a `ForeignKey` or `OneToOneField` directly at an SDK model with a plain `related_name`, installation will fail with:
    ```text
    ValueError: CustomModel 'Biography' declares related_name='biography' on field 'staff'
    targeting SDK model 'Staff'. To prevent collisions across plugins, use a namespaced
    related_name like related_name='%(app_label)s_biography', or related_name='+' to
    disable the reverse relation.
    ```
**Fix:** Either switch to a proxy target (Approach 1) or add the `%(app_label)s_` prefix to your `related_name` (Approach 2).
This validation applies to `ForeignKey` and `OneToOneField`. Fields targeting other CustomModels or proxy models are exempt because those targets are already plugin-private.
* * *
##  See Also 
  - [CustomModels](/sdk/custom-data-custom-models/) \- Defining structured models, relationships, and queries
  - [AttributeHubs](/sdk/custom-data-attribute-hubs/) \- Standalone key-value storage
  - [Design Considerations](/sdk/custom-data-design-considerations/) \- Choosing the right technique and avoiding anti-patterns
  - [Transactions](/sdk/custom-data-transactions/) \- All-or-nothing writes with `transaction.atomic()`
  - [Sharing Data](/sdk/custom-data-sharing-data/) \- Sharing data with other plugins and external services
  - [Data Models](/sdk/data/) \- Core SDK data models
----- END PAGE https://docs.canvasmedical.com/sdk/custom-data-extending-sdk-models/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/custom-data-namespace-lifecycle/
Every plugin that uses custom data operates within a **namespace** — an isolated PostgreSQL schema that holds the plugin's AttributeHubs and CustomModels. During iterative development, namespaces accumulate tables, columns, and data that can get in the way. This page explains how namespaces are created and managed, and how to use the Canvas CLI to inspect and clean up namespaces as you work.
##  Namespace Naming Rules 
Namespace names use the format `org__name` (two parts separated by a double underscore). Each part must start with a lowercase letter and contain only lowercase letters, digits, and single underscores. The total length must not exceed **63 characters** (PostgreSQL's identifier limit).
Examples of valid names: `acme__shared_data`, `myorg__analytics`
##  How Namespaces Are Created 
A namespace is created automatically when the **first plugin** with `"access": "read_write"` is installed into it. The installation process:
  1. Creates a PostgreSQL schema named after the namespace
  2. Establishes two authentication keys (`namespace_read_access_key` and `namespace_read_write_access_key`) — auto-generated UUIDs by default, or values supplied by the installer (see [Pre-Supplying Keys at Creation](/sdk/custom-data-sharing-data/#pre-supplying-keys-at-creation))
  3. Stores the keys as secrets in the plugin
  4. Creates any CustomModel tables defined by the plugin
Subsequent plugins can join the namespace with the appropriate access key. See [Sharing Data](/sdk/custom-data-sharing-data/) for details on multi-plugin namespaces.
###  Installation Flow 
![](/assets/images/sdk/custom-data/installation_flowchart.jpg)
The path depends on the declared access level and whether the namespace already exists:
Declared Access | Namespace Exists | Key Provided | Result  
---|---|---|---  
`read_write` | No | Neither | Creates namespace and tables; auto-generates both keys  
`read_write` | No | Both `namespace_read_access_key` and `namespace_read_write_access_key` | Creates namespace and tables using the supplied keys (see [Pre-Supplying Keys at Creation](/sdk/custom-data-sharing-data/#pre-supplying-keys-at-creation))  
`read_write` | Yes | Valid `namespace_read_write_access_key` | Plugin installed, tables created  
`read_write` | Yes | Invalid or missing | Installation fails  
`read` | Yes | Valid `namespace_read_access_key` | Plugin installed with read access  
`read` | No | N/A | Installation fails  
`read` | Yes | Invalid or missing | Installation fails  
##  Development Workflow 
When developing a plugin with custom data, you'll typically iterate through cycles of changing your models, reinstalling the plugin, and testing. Each reinstall can leave behind tables from previous iterations — renamed models leave orphaned tables, and test data accumulates. The `canvas namespace` CLI commands let you inspect what's in a namespace and clean it up without having to connect to the database directly.
###  Typical Iteration Cycle 
  1. Edit your CustomModel definitions or manifest
  2. Reinstall the plugin: `canvas install my_plugin --host dev-instance`
  3. Test your changes
  4. If models were renamed or removed, use `canvas namespace reset` to clean up orphaned tables
  5. Repeat
##  CLI Commands 
All namespace commands require a running Canvas instance. Pass `--host` to specify which instance to connect to.
###  Listing Namespaces 
See all custom data namespaces on an instance:
    ```bash
    canvas namespace list --host dev-instance
    ```
Output shows each namespace with its total table count and the number of custom (non-system) tables:
    ```text
    acme_corp__shared_data    tables: 7    custom: 3
    acme_corp__analytics      tables: 5    custom: 1
    ```
###  Inspecting a Namespace 
View the tables and columns in a specific namespace:
    ```bash
    canvas namespace inspect acme_corp__shared_data --host dev-instance
    ```
Output separates system tables (managed by the framework) from custom tables (defined by your plugin), and shows column details for custom tables:
    ```text
    Namespace: acme_corp__shared_data
    System tables:
      namespace_auth       ~2 rows
      schema_version       ~2 rows
      custom_attribute     ~150 rows
      attribute_hub        ~3 rows
    Custom tables:
      customnote   ~25 rows
        dbid     bigint    not null
        title    text      nullable
        body     text      nullable
      specialty    ~8 rows
        dbid     bigint    not null
        name     character varying    nullable
    ```
This is useful for verifying that your models were created correctly after installation, or for understanding what's in a namespace before deciding whether to reset or drop it.
###  Resetting a Namespace 
Reset drops your custom tables and truncates the data in system tables, but preserves the namespace itself and its authentication keys. This is the right choice when you want to start fresh with your models while keeping the namespace intact for reinstallation.
By default, reset runs in **dry-run mode** and only shows what would happen:
    ```bash
    canvas namespace reset acme_corp__shared_data --host dev-instance
    ```
    ```text
    Namespace: acme_corp__shared_data
    Custom tables to drop:
      customnote    ~25 rows
      specialty     ~8 rows
    Data tables to truncate:
      custom_attribute    ~150 rows
      attribute_hub       ~3 rows
    This is a dry run. To execute, re-run with --execute
    ```
To actually perform the reset, add `--execute`. You will be prompted to confirm by typing the full namespace name:
    ```bash
    canvas namespace reset acme_corp__shared_data --host dev-instance --execute
    ```
    ```text
    This will reset namespace 'acme_corp__shared_data'. This cannot be undone.
    Type the full namespace name to confirm: acme_corp__shared_data
    Namespace 'acme_corp__shared_data' has been reset.
      Dropped tables: customnote, specialty
      Truncated tables: custom_attribute, attribute_hub
    ```
After a reset, reinstall your plugin to recreate the tables with your updated model definitions.
###  Dropping a Namespace 
Drop removes the entire namespace — the schema, all tables, all data, and all authentication keys. Use this when you want to completely remove a namespace and start over, or when you're done with a development namespace and want to clean up.
Dry-run mode (default):
    ```bash
    canvas namespace drop acme_corp__shared_data --host dev-instance
    ```
    ```text
    Namespace: acme_corp__shared_data
    All tables that will be dropped:
      attribute_hub        ~3 rows
      custom_attribute     ~150 rows
      customnote           ~25 rows
      schema_version       ~4 rows
      namespace_auth       ~2 rows
      specialty            ~8 rows
    This is a dry run. To execute, re-run with --execute
    ```
To execute:
    ```bash
    canvas namespace drop acme_corp__shared_data --host dev-instance --execute
    ```
After a drop, the next plugin installation with that namespace name will create it from scratch, generating new authentication keys. Any other plugins that were sharing the namespace will need to be reconfigured with the new keys.
##  Uninstalling and Reinstalling a Plugin 
Uninstalling a plugin **deletes its secrets** , including the system-generated `namespace_read_access_key` and `namespace_read_write_access_key`. The namespace schema and its data, however, **survive the uninstall**. This is by design: uninstalling a plugin should never destroy custom data, and a namespace shared by multiple plugins must not be torn down while another plugin is still using it.
Because the namespace still exists, reinstalling the plugin does **not** regenerate the keys — key generation only happens when the namespace is first created. The reinstalled plugin is therefore left without valid access keys and cannot read or write its own data, even though configuring the secrets appeared to succeed.
This applies in production as well as during development. To avoid getting stuck:
  - **Before uninstalling** , copy the namespace keys from the Canvas admin UI into an external secret store such as 1Password. On reinstall, set them back as plugin secrets and the plugin regains access immediately. (To find the keys: open the Canvas admin UI, find the plugin that created the namespace, and read the `namespace_read_access_key` / `namespace_read_write_access_key` secret values.)
  - **If the keys are already lost** , run `canvas namespace drop <namespace> --host <instance> --execute` to remove the namespace, then reinstall. Installation recreates the namespace and generates fresh keys. Any other plugins that were sharing the namespace must be reconfigured with the new keys.
##  When to Reset vs. Drop 
Scenario | Command  
---|---  
You renamed or removed a CustomModel and want to clean up the old table | `reset`  
Test data has accumulated and you want a clean slate | `reset`  
You changed your namespace name in the manifest | `drop` the old, then reinstall  
You're done developing and want to remove all traces | `drop`  
Other plugins share this namespace and you want to preserve their access | `reset` (keys are preserved)  
You want to regenerate the namespace authentication keys | `drop`, then reinstall  
You uninstalled a plugin and a reinstall can't access its data (keys were deleted) | `drop`, then reinstall — or restore the saved keys  
##  See Also 
  - [Quick Start](/sdk/custom-data-quick-start/) \- Get started with custom data in 10 minutes
  - [CustomModels](/sdk/custom-data-custom-models/) \- Define structured database tables
  - [Sharing Data](/sdk/custom-data-sharing-data/) \- Share data between plugins
  - [Testing](/sdk/custom-data-testing/) \- Automated tests for custom data
  - [Design Considerations](/sdk/custom-data-design-considerations/) \- Choosing the right approach
----- END PAGE https://docs.canvasmedical.com/sdk/custom-data-namespace-lifecycle/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/custom-data-quick-start/
##  Getting Started 
To use custom data in your plugin, declare a `custom_data` section in your `CANVAS_MANIFEST.json` with a namespace and access level. The namespace is a unique identifier scoped to your organization (formatted as `organization__name` with a double underscore), and the access level controls whether the plugin can read only or read and write data. When the first `read_write` plugin is installed into a namespace, the system automatically initializes a data namespace, prepares tables, and generates `namespace_read_access_key` and `namespace_read_write_access_key` secrets that control access for other plugins joining the same namespace.
    ```json
    {
      "sdk_version": "0.1.4",
      "plugin_version": "1.0.0",
      "name": "my_plugin",
      "variables": [
        {"name": "namespace_read_write_access_key", "sensitive": false}
      ],
      "custom_data": {
        "namespace": "acme_corp__shared_data",
        "access": "read_write"
      }
    }
    ```
###  Step by Step 
  1. `canvas init`
  2. When prompted for a name, enter `Hello Custom Data`
  3. `cd hello-custom-data/hello_custom_data`
  4. Open `CANVAS_MANIFEST.json` in your preferred editor.
  5. Create a `custom_data` block: 
         ```json
         "custom_data": {
           "namespace": "my_org__hello_custom_data",
           "access": "read_write"
         }
         ```
  6. Add `namespace_read_write_access_key` to the `secrets` array (the key will be generated by the system for you)
  7. Next, create a `models` directory under the root of your plugin hierarchy, sibling to `CANVAS_MANIFEST.json` and `handlers`
  8. Create an `__init__.py` file inside of `models` and open it in your editor.
  9. Declare the following classes within the `__init__.py` file: 
         ```python
         from canvas_sdk.v1.data import Note, ModelExtension
         from canvas_sdk.v1.data.base import CustomModel
         from django.db.models import DO_NOTHING, OneToOneField, TextField
         class CustomNote(Note, ModelExtension):
             """Proxy model — see Extending SDK Models for why this exists."""
             pass
         class NoteTag(CustomModel):
             """Stores a plugin-assigned tag on a note."""
             note = OneToOneField(
                 CustomNote, to_field="dbid", on_delete=DO_NOTHING,
                 related_name="tag", primary_key=True
             )
             tagged_by = TextField()
         ```
  10. Open `handlers/event_handlers.py` in your editor. Update the imports: 
         ```python
         from hello_custom_data.models import CustomNote, NoteTag
         ```
  11. In the code, replace uses of `Note` with `CustomNote`. These objects behave the same as the SDK model. (See [Extending SDK Models](/sdk/custom-data-extending-sdk-models/) for why proxy models are used.)
  12. Add the following lines **after** the `note` reference has been initialized in the code: 
         ```python
         tag, created = NoteTag.objects.get_or_create(
             note=note,
             defaults={"tagged_by": "hello-custom-data"}
         )
         log.info(f"Note tagged by: {tag.tagged_by}")
         ```
  13. Install the plugin to your development environment
  14. Tail the logs with `canvas logs`
  15. Log into Canvas, navigate to a patient chart, and create a new note
In the logs you will see our message: `Note tagged by: hello-custom-data`
What just happened? When you installed the plugin, a new database namespace called `my_org__hello_custom_data` was created. Within the namespace are tables that hold information owned by, and managed by, the `my_org` plugins. The `NoteTag` model you defined turned into a PostgreSQL table with the following structure:
    ```sql
    create table my_org__hello_custom_data.notetag
    (
        note_id   bigint not null primary key,
        tagged_by text
    );
    ```
Creating the `NoteTag` record caused a new row to be inserted into the `notetag` table in the `my_org__hello_custom_data` namespace. This table is private to the namespace. The Note itself is unmodified — the `NoteTag` CustomModel stores the additional data in its own table and links back to the note via a `OneToOneField`.
[CustomModels](/sdk/custom-data-custom-models) let you define fully structured tables with typed fields and relationships — including linking to SDK models like Note, Patient, and Staff via `OneToOneField` or `ForeignKey`.
###  AttributeHub Alternative 
If you don't need a structured model and just want to store a simple key-value pair, you can use an [AttributeHub](/sdk/custom-data-attribute-hubs/) instead. Replace the `NoteTag` creation in `event_handlers.py` with:
    ```python
    from canvas_sdk.v1.data import AttributeHub
    from logger import log
    note_id = "89992c23-c298-4118-864a-26cb3e1ae822"
    hub = AttributeHub.objects.create(
        type="note_tag",
        id=f"note:{note_id}"
    )
    hub.set_attribute("tagged_by", "hello-custom-data")
    log.info(f"Note tagged by: {hub.get_attribute('tagged_by')}")
    ```
AttributeHubs are standalone key-value stores — they don't require a model definition or a `models` directory. They're a good fit for one-off state, configuration, and data that doesn't have a natural schema. See [Design Considerations](/sdk/custom-data-design-considerations/) for help choosing between the two approaches.
##  See Also 
  - [Extending SDK Models](/sdk/custom-data-extending-sdk-models/) \- Why proxy models exist and how `related_name` namespacing works
  - [Transactions](/sdk/custom-data-transactions/) \- All-or-nothing writes with `transaction.atomic()`
  - [Testing Custom Data](/sdk/custom-data-testing/) \- Testing utilities and examples
  - [Sharing Data](/sdk/custom-data-sharing-data/) \- Sharing data with other plugins and external services
  - [Data Models](/sdk/data/) \- Core SDK data models
  - [Caching API](/sdk/caching) \- Auto-expiring transient data
  - [Simple API](/sdk/handlers-simple-api/) \- Simple API for sharing data between plugins
  - [Secrets](/sdk/secrets/) \- Managing API keys and sensitive configuration
----- END PAGE https://docs.canvasmedical.com/sdk/custom-data-quick-start/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/custom-data-sharing-data/
Plugins can share data in two ways, depending on the relationship between the plugins:
Approach | Use Case | Coupling  
---|---|---  
**Namespace Sharing** | Plugins owned by the same organization that need direct database access | Tight  
**API Sharing** | Plugins owned by different organizations, or when loose coupling is preferred | Loose  
##  Namespace Sharing 
Namespace sharing allows multiple plugins to read from and write to the same database tables. This is ideal for organizations that want to build smaller, focused plugins instead of a single monolithic plugin. A plugin may reside within one namespace only. If it needs to access data from multiple namespaces, then it must do so via API calls.
###  When to Use Namespace Sharing 
  - Your organization owns multiple plugins that need to share data
  - You want to break a large plugin into smaller, maintainable pieces
  - You need direct database access for performance
  - You want to avoid the overhead of API calls between plugins
###  Namespace Lifecycle 
For a full overview of how namespaces are created, managed, and cleaned up during development, see [Namespace Lifecycle](/sdk/custom-data-namespace-lifecycle/).
###  Discovering Access Keys 
After the namespace is created, you can find the generated keys in the **Canvas admin UI** :
  1. Navigate to **Settings → Plugins**
  2. Find the plugin that created the namespace
  3. Click to view plugin details
  4. The `namespace_read_access_key` and `namespace_read_write_access_key` appear in the **Secrets** section
Share these keys securely with developers of other plugins that need access:
  - Share `namespace_read_access_key` with plugins that only need to read data
  - Share `namespace_read_write_access_key` with plugins that need to modify data
> **Important:** Store these keys in a secure location outside of Canvas, such as 1Password. Removing a key from the manifest's `secrets` array does **not** delete the stored value — it is preserved. However, **uninstalling the plugin deletes its secrets** , including the namespace keys. Because the namespace itself survives an uninstall, a later reinstall will not regenerate the keys, so a copy kept outside Canvas is the only way to restore access. See [Uninstalling and Reinstalling a Plugin](/sdk/custom-data-namespace-lifecycle/#uninstalling-and-reinstalling-a-plugin).
###  Pre-Supplying Keys at Creation 
By default, Canvas auto-generates the two access keys the first time a `read_write` plugin creates a namespace. You can also **supply both keys yourself** at that first install — pass them through the same `--secret` mechanism used for joins:
    ```bash
    canvas install my_plugin \
      --host demo.canvasmedical.com \
      --secret namespace_read_access_key=<your-read-key> \
      --secret namespace_read_write_access_key=<your-read-write-key>
    ```
When both keys are present at the install that creates the namespace, those plaintext values are what get hashed into the namespace's authentication table — Canvas does **not** generate fresh UUIDs.
Rules:
  - Supply **both** keys for your values to take effect. If you provide only one — or leave either value empty — Canvas silently ignores the supplied keys and auto-generates both instead. The install still succeeds; it does not fail.
  - Format is not enforced, but UUID4s are conventional.
  - This only applies to the install that **creates** the namespace. Subsequent joins validate against whatever was written at creation time.
  - If you supply neither key, behavior is unchanged: Canvas generates both for you.
When to use this:
  - You want the keys to be known and persisted outside Canvas before the namespace exists (for example, a deployment system that needs to seed sibling plugins with the same key without round-tripping through the Admin UI).
  - You're restoring access to a previously-dropped namespace and want to reuse known key values.
  - You want deterministic key values across test runs in CI.
###  Configuring Plugin Access 
Each plugin that joins a namespace must:
  1. **Declare the namespace** in `CANVAS_MANIFEST.json`
  2. **Include the access key name** in the manifest's `variables` array
  3. **Provide the access key** during installation
    ```json
    {
      "variables": [
        {"name": "namespace_read_write_access_key", "sensitive": false}
      ],
      "custom_data": {
        "namespace": "acme_corp__shared_data",
        "access": "read_write"
      }
    }
    ```
> Namespace access keys are declared with `"sensitive": false` so the value remains readable in the Admin UI's Secrets inline — that's the surface developers use to copy a key into a sibling plugin that joins the same namespace.
**Installing with the Canvas CLI (recommended):**
Provide the access key using the `--secret` flag:
    ```bash
    canvas install my_plugin \
      --host demo.canvasmedical.com \
      --secret namespace_read_write_access_key=3b35fad9-6462-4e83-83f5-c0e4bde49b71
    ```
**Alternative: Setting secrets via Admin UI:**
If you've already installed the plugin without the secret:
  1. Go to **Settings → Plugins → Your Plugin → Secrets**
  2. Set the `namespace_read_access_key` or `namespace_read_write_access_key` value
  3. **Reinstall the plugin** to pick up the secret
###  Manifest Configuration 
    ```json
    {
      "sdk_version": "0.1.4",
      "plugin_version": "1.0.0",
      "name": "my_plugin",
      "variables": [
        {"name": "namespace_read_write_access_key", "sensitive": false}
      ],
      "custom_data": {
        "namespace": "acme_corp__shared_data",
        "access": "read_write"
      }
    }
    ```
**Namespace naming requirements:**
  - Must contain `__` (double underscore) to separate organization from name
  - Cannot use reserved PostgreSQL names (`public`, `pg_catalog`, etc.)
  - Organizations and names must start with a letter
**Access levels:**
  - `read` \- Can only read data from the namespace
  - `read_write` \- Can read and write data, and create custom tables
###  Permissions and Restrictions 
Permission | `read` | `read_write`  
---|---|---  
Query AttributeHubs | ✅ | ✅  
Query CustomModels | ✅ | ✅  
Create/update/delete AttributeHubs | ❌ | ✅  
Create/update/delete CustomModel records | ❌ | ✅  
Create/update custom database tables | ❌ | ✅  
###  Example: Sharing AttributeHubs 
AttributeHubs store standalone key-value data not attached to Canvas models.
**Plugin A (write access) - Creates configuration hub:**
    ```python
    # CANVAS_MANIFEST.json: "access": "read_write"
    from canvas_sdk.v1.data import AttributeHub
    # Create or retrieve a configuration hub
    config, created = AttributeHub.objects.get_or_create(type="clinic_config", id="main")
    config.set_attribute("max_daily_appointments", 50)
    config.set_attribute("appointment_duration_minutes", 30)
    config.set_attribute("accepting_new_patients", True)
    ```
**Plugin B (read access) - Reads configuration:**
    ```python
    # CANVAS_MANIFEST.json: "access": "read"
    from canvas_sdk.v1.data import AttributeHub
    config = AttributeHub.objects.with_only(
        attribute_names=["max_daily_appointments", "appointment_duration_minutes"]
    ).get(type="clinic_config", id="main")
    max_appointments = config.get_attribute("max_daily_appointments")  # 50
    duration = config.get_attribute("appointment_duration_minutes")  # 30
    ```
###  Example: Sharing CustomModels 
CustomModels allow you to define your own database tables with full ORM support.
**Important:** If multiple plugins need to share the same custom tables, each plugin must declare identical model definitions. The `read_write` plugin creates the tables; `read` plugins can query but not modify them.
**Shared model definition (must be identical in both plugins):**
    ```python
    # models/specialty.py
    from django.db import models
    from canvas_sdk.v1.data.base import CustomModel
    class Specialty(CustomModel):
        """A medical specialty that can be assigned to staff members."""
        name = models.CharField(max_length=100, unique=True)
        description = models.TextField(blank=True)
        requires_referral = models.BooleanField(default=False)
        class Meta:
            indexes = [
                models.Index(fields=['name']),
            ]
    ```
**Plugin A (write access) - Creates and manages specialties:**
    ```python
    # CANVAS_MANIFEST.json: "access": "read_write"
    from .models.specialty import Specialty
    # Create specialties
    cardiology = Specialty(
        name="Cardiology",
        description="Heart and cardiovascular system",
        requires_referral=True
    )
    cardiology.save()
    dermatology = Specialty(
        name="Dermatology",
        description="Skin conditions",
        requires_referral=False
    )
    dermatology.save()
    ```
**Plugin B (read access) - Queries specialties:**
    ```python
    # CANVAS_MANIFEST.json: "access": "read"
    from .models.specialty import Specialty
    # Query specialties (read operations work)
    referral_specialties = Specialty.objects.filter(requires_referral=True)
    for specialty in referral_specialties:
        print(f"{specialty.name}: {specialty.description}")
    # Write operations raise NamespaceWriteDenied
    specialty = Specialty.objects.first()
    specialty.description = "Updated"
    specialty.save()  # Raises NamespaceWriteDenied!
    ```
###  Error Handling 
When a plugin with `read` access attempts a write operation, a `NamespaceWriteDenied` exception is raised:
    ```python
    from canvas_sdk.v1.data.base import NamespaceWriteDenied
    try:
        hub.set_attribute("key", "value")
    except NamespaceWriteDenied as e:
        # "Write operation denied: namespace 'acme_corp__shared_data' is read-only.
        #  Plugin must declare 'read_write' access to perform write operations."
        log.error(f"Cannot write to shared namespace: {e}")
    ```
###  Troubleshooting 
**"NamespaceAccessError: secret 'namespace_read_access_key' is not configured"**
  - Add the secret name to the `secrets` array in your manifest
  - Ensure the secret has a value set in the Canvas UI
**"NamespaceAccessError: the key value is not a valid access key"**
  - Verify you're using the correct key from the namespace owner
  - Check that the key hasn't been regenerated
**"NamespaceAccessError: requests 'read_write' access but key only grants 'read'"**
  - You're using `namespace_read_access_key` but declared `"access": "read_write"`
  - Either change to `namespace_read_write_access_key` or change access to `"read"`
**"NamespaceWriteDenied: namespace is read-only"**
  - Your plugin has `"access": "read"` but is attempting a write operation
  - Change to `"access": "read_write"` and use `namespace_read_write_access_key`
##  API Sharing 
API sharing is the recommended approach when:
  - Plugins are owned by different organizations
  - You want loose coupling between plugins
  - You need fine-grained control over what data is exposed
  - You want to version your data interface independently
###  Example: Exposing Provider Profile Data 
    ```python
    from canvas_sdk.handlers.simple_api import SimpleAPI, APIKeyCredentials, api
    from canvas_sdk.effects.simple_api import JSONResponse
    from canvas_sdk.v1.data import Staff, ModelExtension
    from canvas_sdk.v1.data.base import CustomModel
    from django.db.models import BooleanField, DO_NOTHING, OneToOneField, TextField
    class CustomStaff(Staff, ModelExtension):
        pass
    class StaffProfile(CustomModel):
        staff = OneToOneField(
            CustomStaff, to_field="dbid", on_delete=DO_NOTHING,
            related_name="profile"
        )
        specialty = TextField()
        accepting_patients = BooleanField(default=True)
    class ProfileAPI(SimpleAPI):
        """API to share staff profile data with authorized plugins."""
        PREFIX = "/staff-profiles"
        def authenticate(self, credentials: APIKeyCredentials) -> bool:
            """Validate API key from requesting plugin."""
            from hmac import compare_digest
            provided_key = credentials.key
            expected_key = self.secrets["profile_api_key"]
            return compare_digest(provided_key.encode(), expected_key.encode())
        @api.get("/<staff_id>")
        def get_profile(self):
            """Return staff profile data."""
            staff_id = self.request.path_params["staff_id"]
            staff = CustomStaff.objects.select_related("profile").get(id=staff_id)
            # Explicitly choose what data to expose
            profile = {
                "staff_id": staff.id,
                "first_name": staff.first_name,
                "last_name": staff.last_name,
                "specialty": staff.profile.specialty,
                "accepting_patients": staff.profile.accepting_patients
            }
            return [JSONResponse(profile)]
    ```
###  Consuming Shared Data from Another Plugin 
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.simple_api import Response, JSONResponse
    from canvas_sdk.handlers.simple_api import SimpleAPI, api
    from canvas_sdk.utils import Http
    class MyAPI(SimpleAPI):
        PREFIX = "/retrieve"
        @api.get("/profile_for_staff/<staff_id>")
        def get_single_profile_via_api(self) -> list[Response | Effect]:
            staff_id = self.request.path_params["staff_id"]
            canvas_host = f"{self.environment['CUSTOMER_IDENTIFIER']}.canvasmedical.com"
            token = self.secrets["profile_api_token"]
            other_plugin_api = f"https://{canvas_host}/plugin-io/api/other_plugin/staff-profiles/{staff_id}"
            http = Http()
            response = http.get(other_plugin_api, headers={"Authorization": token})
            return [JSONResponse(response.json())]
    ```
###  API Sharing Best Practices 
  1. **Explicit Authorization** \- Always require authentication for APIs that expose plugin data
  2. **Minimal Exposure** \- Only expose the specific data fields that are necessary
  3. **Validate Requests** \- Check permissions and validate that the requester should have access
  4. **Document APIs** \- Provide clear documentation for plugins that will consume your API
  5. **Version APIs** \- Use versioning (e.g., `/v1/profiles`) to allow API evolution
  6. **Audit Access** \- Log API access for security and debugging purposes
  7. **Rate Limiting** \- Consider implementing rate limits to prevent abuse
###  Security Considerations 
  - **Never bypass plugin isolation** by attempting to access another plugin's database schema directly
  - **Use API keys or tokens** stored in secrets, never hardcoded in plugin code
  - **Implement proper error handling** that doesn't leak sensitive information
  - **Consider PHI implications** when exposing patient-related data via APIs
  - **Follow least privilege** principle - grant minimum necessary access
##  Choosing Between Namespace and API Sharing 
Factor | Namespace Sharing | API Sharing  
---|---|---  
**Ownership** | Same organization | Different organizations  
**Coupling** | Tight | Loose  
**Performance** | Direct DB access | HTTP overhead  
**Schema Evolution** | Coordinated updates | Independent versioning  
**Access Control** | Binary (read/read_write) | Fine-grained  
**Setup Complexity** | Lower | Higher  
##  See Also 
  - [Custom Data Overview](/sdk/custom-data/) \- Introduction to custom data storage
  - [Namespace Lifecycle](/sdk/custom-data-namespace-lifecycle/) \- Managing namespaces during development
  - [AttributeHubs](/sdk/custom-data-attribute-hubs/) \- Standalone key-value storage
  - [CustomModels](/sdk/custom-data-custom-models/) \- Django models for structured data
  - [Testing Utils](/sdk/testing-utils/) \- Factories for testing custom data
  - [Caching API](/sdk/caching) \- Auto-expiring transient data
  - [Simple API](/sdk/handlers-simple-api-http) \- HTTP API handlers
  - [Secrets](/sdk/secrets/) \- Managing API keys and sensitive configuration
----- END PAGE https://docs.canvasmedical.com/sdk/custom-data-sharing-data/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/custom-data-testing/
The Canvas SDK provides comprehensive testing utilities for custom data. Tests run within database transactions that automatically roll back, ensuring isolation between test cases.
##  Test Setup 
Install the test utilities extra to enable pytest-based testing:
    ```bash
    uv add "canvas[test-utils]"
    ```
Run your tests with:
    ```bash
    uv run pytest
    ```
Each test runs inside a transaction and automatically rolls back at the end, providing clean isolation without manual cleanup.
See [Testing Utilities](/sdk/testing-utils/) for complete setup instructions.
##  Creating Factories for Extended Models 
Define factories for extended models by extending the base SDK factories:
    ```python
    import factory
    from canvas_sdk.test_utils.factories import StaffFactory, PatientFactory
    from staff_plus.models import CustomStaff, CustomPatient
    class CustomStaffFactory(StaffFactory, factory.django.DjangoModelFactory[CustomStaff]):
        """Factory for creating CustomStaff instances."""
        class Meta:
            model = CustomStaff
    class CustomPatientFactory(PatientFactory, factory.django.DjangoModelFactory[CustomPatient]):
        """Factory for creating CustomPatient instances."""
        class Meta:
            model = CustomPatient
    ```
##  Creating Factories for Custom Models 
Define factories for your custom models with appropriate field values:
    ```python
    import factory
    from my_plugin.models import Specialty, StaffSpecialty
    from my_plugin.models import Biography
    class SpecialtyFactory(factory.django.DjangoModelFactory):
        """Factory for creating Specialty instances."""
        class Meta:
            model = Specialty
            django_get_or_create = ("name",)  # Avoid duplicate specialties
        name = factory.Faker("random_element", elements=[
            "Cardiology", "Dermatology", "Neurology", "Orthopedics",
            "Pediatrics", "Psychiatry", "Radiology", "Surgery"
        ])
    class BiographyFactory(factory.django.DjangoModelFactory):
        """Factory for creating Biography instances."""
        class Meta:
            model = Biography
        staff = factory.SubFactory(CustomStaffFactory)
        biography = factory.Faker("paragraph", nb_sentences=5)
        language = factory.Faker("language_name")
        practicing_since = factory.Faker("year")
    class StaffSpecialtyFactory(factory.django.DjangoModelFactory):
        """Factory for many-to-many relationship."""
        class Meta:
            model = StaffSpecialty
        staff = factory.SubFactory(CustomStaffFactory)
        specialty = factory.SubFactory(SpecialtyFactory)
    ```
##  Testing AttributeHub 
Test that AttributeHub stores and retrieves data correctly:
    ```python
    from datetime import datetime
    import factory
    from canvas_sdk.test_utils.factories import StaffFactory
    from canvas_sdk.v1.data import AttributeHub, Staff, ModelExtension
    class CustomStaff(Staff, ModelExtension):
        pass
    class CustomStaffFactory(StaffFactory, factory.django.DjangoModelFactory[CustomStaff]):
        class Meta:
            model = CustomStaff
    def test_attribute_hub_creation():
        """Test creating and using AttributeHub."""
        # Create hub
        hub = AttributeHub.objects.create(
            type="staff_profile",
            id="staff_123"
        )
        # Set attributes
        hub.set_attribute("last_sync", datetime.now())
        hub.set_attribute("external_id", "ext_456")
        # Verify persistence
        hub_from_db = AttributeHub.objects.get(dbid=hub.dbid)
        assert hub_from_db.get_attribute("external_id") == "ext_456"
    def test_attribute_hub_get_or_create():
        """Test get_or_create pattern with AttributeHub."""
        staff = CustomStaffFactory.create()
        # First call creates
        hub1, created1 = AttributeHub.objects.get_or_create(
            type="staff_sync",
            id=f"staff:{staff.id}"
        )
        assert created1 is True
        hub1.set_attribute("data", {"key": "value"})
        # Second call retrieves existing
        hub2, created2 = AttributeHub.objects.get_or_create(
            type="staff_sync",
            id=f"staff:{staff.id}"
        )
        assert created2 is False
        assert hub1.dbid == hub2.dbid
        assert hub2.get_attribute("data") == {"key": "value"}
    def test_attribute_hub_json_storage():
        """Test storing complex JSON in AttributeHub."""
        hub = AttributeHub.objects.create(
            type="profile",
            id="test_123"
        )
        profile_data = {
            "biography": "Experienced physician",
            "specialties": ["Cardiology", "Internal Medicine"],
            "languages": ["English", "Spanish"],
            "practicing_since": 2005,
            "accepting_patients": False
        }
        hub.set_attribute("profile", profile_data)
        hub_from_db = AttributeHub.objects.get(dbid=hub.dbid)
        retrieved = hub_from_db.get_attribute("profile")
        assert retrieved == profile_data
        assert retrieved["biography"] == "Experienced physician"
        assert len(retrieved["specialties"]) == 2
    ```
##  Testing Custom Models 
Test custom model creation, relationships, and queries:
    ```python
    import factory
    from datetime import datetime
    from django.db.models import (
        ForeignKey, ManyToManyField, OneToOneField, TextField, IntegerField,
        DateTimeField, Index, DO_NOTHING
    )
    from canvas_sdk.test_utils.factories import StaffFactory
    from canvas_sdk.v1.data import Staff, ModelExtension
    from canvas_sdk.v1.data.base import CustomModel
    class CustomStaff(Staff, ModelExtension):
        pass
    class CustomStaffFactory(StaffFactory, factory.django.DjangoModelFactory[CustomStaff]):
        class Meta:
            model = CustomStaff
    class Specialty(CustomModel):
        class Meta:
            indexes = [
                Index(fields=["name"]),
            ]
        name = TextField()
        staff_members = ManyToManyField(
            "CustomStaff",
            through="StaffSpecialty",
            related_name="specialties",
        )
    class Biography(CustomModel):
        staff = OneToOneField(
            CustomStaff,
            to_field="dbid",
            on_delete=DO_NOTHING,
            related_name="biography"
        )
        biography = TextField()
        language = TextField()
        practicing_since = IntegerField()
    class Language(CustomModel):
        staff = ForeignKey(
            CustomStaff,
            to_field="dbid",
            on_delete=DO_NOTHING,
            related_name="languages"
        )
        name = TextField()
        code = TextField()
        created = DateTimeField(default=datetime.now)
    class StaffSpecialty(CustomModel):
        staff = ForeignKey(
            CustomStaff,
            to_field="dbid",
            on_delete=DO_NOTHING,
            related_name="staff_specialties"
        )
        specialty = ForeignKey(
            Specialty,
            to_field="dbid",
            on_delete=DO_NOTHING,
            related_name="staff_specialties"
        )
    def test_custom_model_creation():
        """Test creating custom models."""
        specialty = Specialty.objects.create(name="Cardiology")
        assert specialty.dbid is not None
        assert specialty.name == "Cardiology"
        # Verify persistence
        specialty_from_db = Specialty.objects.get(dbid=specialty.dbid)
        assert specialty_from_db.name == "Cardiology"
    def test_one_to_one_relationship():
        """Test one-to-one relationships."""
        staff = CustomStaffFactory.create()
        # Create related biography
        biography = Biography.objects.create(
            staff=staff,
            biography="Experienced cardiologist",
            language="English",
            practicing_since=2005
        )
        # Access from biography to staff
        assert biography.staff.id == staff.id
        # Access from staff to biography (reverse relation)
        staff_from_db = CustomStaff.objects.get(id=staff.id)
        assert staff_from_db.biography.biography == "Experienced cardiologist"
        assert staff_from_db.biography.practicing_since == 2005
    def test_one_to_many_relationship():
        """Test one-to-many relationships."""
        staff = CustomStaffFactory.create()
        # Create multiple related languages
        Language.objects.create(staff=staff, name="English", code="en")
        Language.objects.create(staff=staff, name="Spanish", code="es")
        Language.objects.create(staff=staff, name="French", code="fr")
        # Access all languages via reverse relation
        languages = staff.languages.all()
        assert languages.count() == 3
        language_names = [lang.name for lang in languages]
        assert "English" in language_names
        assert "Spanish" in language_names
        assert "French" in language_names
    def test_many_to_many_relationship():
        """Test many-to-many relationships via junction table."""
        staff = CustomStaffFactory.create()
        cardiology = Specialty.objects.create(name="Cardiology")
        internal_med = Specialty.objects.create(name="Internal Medicine")
        # Create associations
        StaffSpecialty.objects.create(staff=staff, specialty=cardiology)
        StaffSpecialty.objects.create(staff=staff, specialty=internal_med)
        # Query specialties for staff
        staff_specialties = staff.staff_specialties.all()
        assert staff_specialties.count() == 2
        specialty_names = [ss.specialty.name for ss in staff_specialties]
        assert "Cardiology" in specialty_names
        assert "Internal Medicine" in specialty_names
        # Query staff by specialty
        staff_ids = (
            StaffSpecialty.objects
            .filter(specialty__name="Cardiology")
            .values_list("staff_id", flat=True)
        )
        assert staff.dbid in staff_ids
    def test_many_to_many_query_filtering():
        """Test querying across many-to-many relationships."""
        staff1 = CustomStaffFactory.create()
        staff2 = CustomStaffFactory.create()
        cardiology = Specialty.objects.create(name="Cardiology")
        neurology = Specialty.objects.create(name="Neurology")
        StaffSpecialty.objects.create(staff=staff1, specialty=cardiology)
        StaffSpecialty.objects.create(staff=staff2, specialty=neurology)
        StaffSpecialty.objects.create(staff=staff2, specialty=cardiology)
        # Find all staff with cardiology
        cardiology_staff_ids = (
            StaffSpecialty.objects
            .filter(specialty__name="Cardiology")
            .values_list("staff_id", flat=True)
        )
        assert staff1.dbid in cardiology_staff_ids
        assert staff2.dbid in cardiology_staff_ids
        # Find staff with multiple specialties
        multi_specialty_ids = (
            StaffSpecialty.objects
            .filter(specialty__name__in=["Cardiology", "Neurology"])
            .values_list("staff_id", flat=True)
            .distinct()
        )
        assert len(multi_specialty_ids) == 2
    def test_many_to_many_through_field():
        """Test direct M2M traversal via ManyToManyField(through=...)."""
        staff = CustomStaffFactory.create()
        cardiology = Specialty.objects.create(name="Cardiology")
        internal_med = Specialty.objects.create(name="Internal Medicine")
        StaffSpecialty.objects.create(staff=staff, specialty=cardiology)
        StaffSpecialty.objects.create(staff=staff, specialty=internal_med)
        # Direct M2M traversal — Specialty → staff
        assert staff in cardiology.staff_members.all()
        # Reverse M2M traversal — staff → specialties
        specialty_names = [s.name for s in staff.specialties.all()]
        assert "Cardiology" in specialty_names
        assert "Internal Medicine" in specialty_names
    ```
##  Testing with Factories 
Use factories to simplify test data creation:
    ```python
    import factory
    from django.db.models import OneToOneField, TextField, IntegerField, DO_NOTHING
    from canvas_sdk.test_utils.factories import StaffFactory
    from canvas_sdk.v1.data import Staff, ModelExtension
    from canvas_sdk.v1.data.base import CustomModel
    class CustomStaff(Staff, ModelExtension):
        pass
    class CustomStaffFactory(StaffFactory, factory.django.DjangoModelFactory[CustomStaff]):
        class Meta:
            model = CustomStaff
    class Biography(CustomModel):
        staff = OneToOneField(
            CustomStaff,
            to_field="dbid",
            on_delete=DO_NOTHING,
            related_name="biography"
        )
        biography = TextField()
        language = TextField()
        practicing_since = IntegerField()
    class BiographyFactory(factory.django.DjangoModelFactory):
        class Meta:
            model = Biography
        staff = factory.SubFactory(CustomStaffFactory)
        biography = factory.Faker("paragraph", nb_sentences=5)
        language = factory.Faker("language_name")
        practicing_since = factory.Faker("year")
    def test_with_factories():
        """Test using factories for quick data setup."""
        # Create staff with biography using factories
        biography = BiographyFactory.create()
        assert biography.staff is not None
        assert biography.biography is not None
        assert biography.practicing_since is not None
        # Factory automatically created the related staff
        staff = biography.staff
        assert staff.first_name is not None
    class StaffSpecialty(CustomModel):
        staff = ForeignKey(
            CustomStaff,
            to_field="dbid",
            on_delete=DO_NOTHING,
            related_name="staff_specialties"
        )
        specialty = ForeignKey(
            Specialty,
            to_field="dbid",
            on_delete=DO_NOTHING,
            related_name="staff_specialties"
        )
    class StaffSpecialtyFactory(factory.django.DjangoModelFactory):
        class Meta:
            model = StaffSpecialty
        staff = factory.SubFactory(CustomStaffFactory)
        specialty = factory.SubFactory(SpecialtyFactory)
    def test_many_to_many_with_factories():
        """Test many-to-many relationships with factories."""
        # Create staff-specialty associations
        ss1 = StaffSpecialtyFactory.create()
        ss2 = StaffSpecialtyFactory.create(staff=ss1.staff)  # Same staff, different specialty
        # Verify relationships
        assert ss1.staff.staff_specialties.count() == 2
    ```
##  Testing Queries and Prefetching 
Test that prefetching and query optimization work correctly:
    ```python
    import factory
    from django.db.models import (
        ForeignKey, OneToOneField, TextField, IntegerField,
        Index, DO_NOTHING, Count
    )
    from canvas_sdk.test_utils.factories import StaffFactory
    from canvas_sdk.v1.data import AttributeHub, Staff, ModelExtension
    from canvas_sdk.v1.data.base import CustomModel
    class CustomStaff(Staff, ModelExtension):
        pass
    class CustomStaffFactory(StaffFactory, factory.django.DjangoModelFactory[CustomStaff]):
        class Meta:
            model = CustomStaff
    class Biography(CustomModel):
        staff = OneToOneField(
            CustomStaff,
            to_field="dbid",
            on_delete=DO_NOTHING,
            related_name="biography"
        )
        biography = TextField()
        practicing_since = IntegerField()
    class Specialty(CustomModel):
        class Meta:
            indexes = [
                Index(fields=["name"]),
            ]
        name = TextField()
    class StaffSpecialty(CustomModel):
        staff = ForeignKey(
            CustomStaff,
            to_field="dbid",
            on_delete=DO_NOTHING,
            related_name="staff_specialties"
        )
        specialty = ForeignKey(
            Specialty,
            to_field="dbid",
            on_delete=DO_NOTHING,
            related_name="staff_specialties"
        )
    class BiographyFactory(factory.django.DjangoModelFactory):
        class Meta:
            model = Biography
        staff = factory.SubFactory(CustomStaffFactory)
        biography = factory.Faker("paragraph")
        practicing_since = factory.Faker("year")
    class SpecialtyFactory(factory.django.DjangoModelFactory):
        class Meta:
            model = Specialty
        name = factory.Faker("word")
    class StaffSpecialtyFactory(factory.django.DjangoModelFactory):
        class Meta:
            model = StaffSpecialty
        staff = factory.SubFactory(CustomStaffFactory)
        specialty = factory.SubFactory(SpecialtyFactory)
    def test_attribute_hub_prefetch():
        """Test prefetching AttributeHub attributes."""
        hub1 = AttributeHub.objects.create(type="profile", id="staff_1")
        hub2 = AttributeHub.objects.create(type="profile", id="staff_2")
        hub1.set_attribute("specialty", "Cardiology")
        hub2.set_attribute("specialty", "Neurology")
        # Query with automatic prefetch (default behavior)
        hubs = AttributeHub.objects.filter(type="profile")
        # Access attributes without additional queries
        for hub in hubs:
            specialty = hub.get_attribute("specialty")
            assert specialty in ["Cardiology", "Neurology"]
    def test_attribute_hub_with_only():
        """Test selective attribute prefetching on AttributeHub."""
        hub = AttributeHub.objects.create(type="profile", id="staff_1")
        hub.set_attributes({
            "specialty": "Cardiology",
            "years_experience": 15,
            "accepting_patients": True
        })
        # Prefetch only specific attributes
        hub_from_db = (
            AttributeHub.objects
            .with_only(["specialty", "accepting_patients"])
            .get(dbid=hub.dbid)
        )
        # Prefetched attributes accessible
        assert hub_from_db.get_attribute("specialty") == "Cardiology"
        assert hub_from_db.get_attribute("accepting_patients") is True
    def test_relationship_prefetch():
        """Test prefetching related models."""
        staff1 = CustomStaffFactory.create()
        staff2 = CustomStaffFactory.create()
        BiographyFactory.create(staff=staff1)
        BiographyFactory.create(staff=staff2)
        cardiology = SpecialtyFactory.create(name="Cardiology")
        StaffSpecialtyFactory.create(staff=staff1, specialty=cardiology)
        StaffSpecialtyFactory.create(staff=staff2, specialty=cardiology)
        # Prefetch all relationships
        all_staff = (
            CustomStaff.objects
            .prefetch_related("biography")
            .prefetch_related("staff_specialties__specialty")
            .all()
        )
        # Access without additional queries
        for staff in all_staff:
            bio = staff.biography.biography
            specialties = [ss.specialty.name for ss in staff.staff_specialties.all()]
            assert bio is not None
            assert len(specialties) > 0
    def test_select_related():
        """Test select_related for FK and OneToOne joins."""
        staff = CustomStaffFactory.create()
        BiographyFactory.create(staff=staff)
        StaffSpecialtyFactory.create(staff=staff)
        # select_related eagerly loads FK/O2O relations in a single query
        specialty_assoc = (
            StaffSpecialty.objects
            .select_related("staff", "specialty")
            .filter(staff=staff)
            .first()
        )
        assert specialty_assoc.staff.first_name is not None
        assert specialty_assoc.specialty.name is not None
    ```
##  Testing Data Integrity 
Test data validation, constraints, and cascade behavior:
    ```python
    from datetime import datetime
    import factory
    import pytest
    from django.db import IntegrityError
    from django.db.models import (
        CASCADE, DateTimeField, ForeignKey, TextField, Index,
        UniqueConstraint, DO_NOTHING
    )
    from canvas_sdk.test_utils.factories import StaffFactory
    from canvas_sdk.v1.data import AttributeHub, Staff, ModelExtension
    from canvas_sdk.v1.data.base import CustomModel
    class CustomStaff(Staff, ModelExtension):
        pass
    class CustomStaffFactory(StaffFactory, factory.django.DjangoModelFactory[CustomStaff]):
        class Meta:
            model = CustomStaff
    class Specialty(CustomModel):
        class Meta:
            indexes = [
                Index(fields=["name"]),
            ]
        name = TextField()
    class SpecialtyFactory(factory.django.DjangoModelFactory):
        class Meta:
            model = Specialty
        name = factory.Faker("word")
    class StaffSpecialty(CustomModel):
        staff = ForeignKey(
            CustomStaff,
            to_field="dbid",
            on_delete=DO_NOTHING,
            related_name="staff_specialties"
        )
        specialty = ForeignKey(
            Specialty,
            to_field="dbid",
            on_delete=DO_NOTHING,
            related_name="staff_specialties"
        )
    class StaffSpecialtyFactory(factory.django.DjangoModelFactory):
        class Meta:
            model = StaffSpecialty
        staff = factory.SubFactory(CustomStaffFactory)
        specialty = factory.SubFactory(SpecialtyFactory)
    class Team(CustomModel):
        class Meta:
            constraints = [
                UniqueConstraint(fields=["name"], name="unique_team_name"),
            ]
        name = TextField()
    class TeamMember(CustomModel):
        class Meta:
            constraints = [
                UniqueConstraint(
                    fields=["team", "staff"],
                    name="unique_team_staff",
                ),
            ]
        team = ForeignKey(Team, to_field="dbid", on_delete=CASCADE, related_name="members")
        staff = ForeignKey(
            CustomStaff, to_field="dbid", on_delete=DO_NOTHING, related_name="team_memberships"
        )
        joined_at = DateTimeField()
    class TeamFactory(factory.django.DjangoModelFactory):
        class Meta:
            model = Team
        name = factory.Sequence(lambda n: f"Team {n + 1}")
    class TeamMemberFactory(factory.django.DjangoModelFactory):
        class Meta:
            model = TeamMember
        team = factory.SubFactory(TeamFactory)
        staff = factory.SubFactory(CustomStaffFactory)
        joined_at = factory.LazyFunction(datetime.now)
    def test_manual_cleanup_on_delete():
        """Test manual cleanup for DO_NOTHING foreign keys.
        ForeignKeys to SDK models (Staff, Patient, etc.) must use DO_NOTHING
        because those tables are managed externally. Related records must be
        deleted manually before deleting the parent.
        """
        staff = CustomStaffFactory.create()
        specialty = SpecialtyFactory.create()
        ss = StaffSpecialtyFactory.create(staff=staff, specialty=specialty)
        # With DO_NOTHING, you must clean up related records manually
        specialty_id = specialty.dbid
        StaffSpecialty.objects.filter(specialty_id=specialty_id).delete()
        specialty.delete()
        # Verify both are gone
        assert not StaffSpecialty.objects.filter(specialty_id=specialty_id).exists()
        assert not Specialty.objects.filter(dbid=specialty_id).exists()
    def test_cascade_delete():
        """Test CASCADE deletion between custom models.
        ForeignKeys between your own CustomModels can use CASCADE to
        automatically delete related records.
        """
        team = TeamFactory.create()
        TeamMemberFactory.create(team=team)
        TeamMemberFactory.create(team=team)
        assert TeamMember.objects.filter(team=team).count() == 2
        # Deleting the team cascades to members
        team.delete()
        assert not TeamMember.objects.filter(team=team).exists()
    def test_unique_constraint_violation():
        """Test that UniqueConstraint prevents duplicate records."""
        team = TeamFactory.create()
        staff = CustomStaffFactory.create()
        TeamMember.objects.create(team=team, staff=staff, joined_at=datetime.now())
        # Same team + staff violates the UniqueConstraint
        with pytest.raises(IntegrityError):
            TeamMember.objects.create(team=team, staff=staff, joined_at=datetime.now())
    def test_attribute_hub_upsert():
        """Test that set_attribute updates existing values rather than creating duplicates."""
        hub = AttributeHub.objects.create(type="test", id="upsert_test")
        # Set attribute
        hub.set_attribute("field", "value1")
        # Setting same attribute name should update, not create duplicate
        hub.set_attribute("field", "value2")
        # Verify only the updated value exists
        hub_from_db = AttributeHub.objects.get(dbid=hub.dbid)
        assert hub_from_db.get_attribute("field") == "value2"
    def test_transaction_rollback():
        """Verify that tests automatically roll back."""
        # This test demonstrates automatic rollback
        # Data created here won't exist in subsequent tests
        staff = CustomStaffFactory.create()
        staff_id = staff.id
        specialty = SpecialtyFactory.create(name="Test Specialty")
        # After this test, these objects won't exist in other tests
        # due to automatic transaction rollback
        assert staff_id is not None
        assert specialty.name == "Test Specialty"
    ```
##  Testing proxy_field 
The `proxy_field` descriptor lets a `ModelExtension` proxy transparently return another proxy class from a ForeignKey lookup, so you can access custom methods on related objects:
    ```python
    from canvas_sdk.v1.data import Note, Patient, Staff, ModelExtension
    from canvas_sdk.v1.data.base import proxy_field
    from canvas_sdk.test_utils.factories import NoteFactory
    class CustomPatient(Patient, ModelExtension):
        @property
        def display_name(self) -> str:
            return f"{self.first_name} {self.last_name}"
    class CustomNote(Note, ModelExtension):
        # Without proxy_field, accessing note.patient returns a plain Patient.
        # With proxy_field, it returns a CustomPatient instead.
        patient = proxy_field(CustomPatient)
    def test_proxy_field_returns_proxy_class():
        """proxy_field swaps __class__ so the returned object is CustomPatient."""
        note = NoteFactory.create()
        custom_note = CustomNote.objects.select_related("patient").get(dbid=note.dbid)
        # The patient is a CustomPatient, not a plain Patient
        assert type(custom_note.patient) is CustomPatient
        assert custom_note.patient.display_name == (
            f"{note.patient.first_name} {note.patient.last_name}"
        )
    def test_proxy_field_handles_null():
        """proxy_field returns None when the FK is null."""
        note = NoteFactory.create(patient=None)
        custom_note = CustomNote.objects.get(dbid=note.dbid)
        assert custom_note.patient is None
    ```
##  Testing Best Practices 
  1. **Use factories** for consistent test data generation
  2. **Test isolation** \- Each test should be independent and not rely on data from other tests
  3. **Test both directions** of relationships (forward and reverse)
  4. **Verify persistence** by reloading objects from the database
  5. **Test edge cases** like None values, empty lists, and missing relationships
  6. **Use descriptive test names** that explain what is being tested
  7. **Test query optimization** to ensure prefetching works as expected
  8. **Verify constraints** like uniqueness behavior
  9. **Choose the right`on_delete`** \- ForeignKeys to SDK models (Staff, Patient, etc.) must use `DO_NOTHING` and related records must be deleted manually. ForeignKeys between your own CustomModels can use `CASCADE` for automatic cleanup
##  See Also 
  - [Custom Data Overview](/sdk/custom-data/) \- Introduction to custom data storage
  - [CustomModels](/sdk/custom-data-custom-models/) \- Django models for structured data
  - [AttributeHubs](/sdk/custom-data-attribute-hubs/) \- Standalone key-value storage
  - [Sharing Data](/sdk/custom-data-sharing-data/) \- Sharing data among plugins
  - [Caching API](/sdk/caching) \- Auto-expiring transient data
----- END PAGE https://docs.canvasmedical.com/sdk/custom-data-testing/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/custom-data-transactions/
##  Overview 
By default, each ORM operation in a plugin (`.save()`, `.create()`, `.update()`, `.delete()`) is committed to the database immediately. There is no automatic transaction wrapping your handler or protocol — if you perform three writes and the third one fails, the first two are already committed.
When you need multiple operations to succeed or fail together, use `transaction.atomic()`.
* * *
##  Using `transaction.atomic()`
Wrap related operations in an `atomic()` block to ensure all-or-nothing behavior:
    ```python
    from django.db.transaction import atomic
    with atomic():
        # All operations inside this block are part of a single transaction.
        # If any operation raises an exception, everything is rolled back.
        specialty, _ = Specialty.objects.get_or_create(name="Cardiology")
        StaffSpecialty.objects.filter(staff=staff).delete()
        StaffSpecialty.objects.bulk_create([
            StaffSpecialty(staff=staff, specialty=specialty)
        ])
        Biography.objects.create(staff=staff, biography="...")
    ```
If an exception occurs anywhere inside the block, all changes are rolled back — the database is left as it was before the block started.
* * *
##  When to Use Transactions 
Use `transaction.atomic()` when your handler performs **multiple related writes** that should not be partially applied:
  - **Replacing associations** — deleting existing records and creating new ones (e.g., replacing a staff member's specialties). Without a transaction, a failure after the delete leaves the staff member with no specialties.
  - **Creating a parent and its children** — e.g., creating a `Biography` and several `Language` records in one request. A partial failure could leave orphaned or incomplete data.
  - **Coordinated updates** — updating multiple models that must stay consistent with each other.
You do **not** need a transaction for:
  - A single `.create()`, `.save()`, or `.update()` call — these are already atomic on their own.
  - Read-only operations — `SELECT` queries don't modify data.
* * *
##  Example: Multi-Model Upsert 
This example accepts a JSON payload and upserts a staff profile spanning multiple CustomModels. The `atomic()` block ensures that either the entire profile is saved or nothing is:
    ```python
    from django.db.transaction import atomic
    from canvas_sdk.effects.simple_api import JSONResponse
    from canvas_sdk.handlers.simple_api import SimpleAPI, api
    class ProfileAPI(SimpleAPI):
        PREFIX = "/profile"
        @api.post("/v2/<staff_id>")
        def post_profile(self):
            with atomic():
                staff_id = self.request.path_params["staff_id"]
                json_body = self.request.json()
                staff = CustomStaff.objects.get(id=staff_id)
                # Upsert languages
                for name in json_body.get("languages", []):
                    Language.objects.get_or_create(name=name, staff=staff)
                # Replace specialty associations
                specialties = []
                for name in json_body.get("specialties", []):
                    specialty, _ = Specialty.objects.get_or_create(name=name)
                    specialties.append(specialty)
                StaffSpecialty.objects.filter(staff=staff).delete()
                StaffSpecialty.objects.bulk_create([
                    StaffSpecialty(staff=staff, specialty=s) for s in specialties
                ])
                # Upsert biography
                biography_text = json_body.get("biography")
                Biography.objects.update_or_create(
                    staff=staff,
                    defaults={
                        "biography": biography_text,
                        "practicing_since": json_body.get("practicing_since"),
                        "is_accepting_patients": json_body.get("accepting_patients"),
                    },
                )
            return [JSONResponse({"status": "ok"})]
    ```
If any operation inside the `atomic()` block raises an exception — a constraint violation, an unexpected data type, a model validation error — the entire block is rolled back and no partial data is written.
* * *
##  How It Works 
Plugin code runs inside a database context that sets the PostgreSQL `search_path` to the plugin's namespace. `transaction.atomic()` operates on this same connection automatically — no `using=` parameter is needed.
Under the hood, `atomic()` issues a `SAVEPOINT` (for nested usage) or manages the transaction directly. When the block exits cleanly, the transaction is committed. When an exception propagates out, it is rolled back.
* * *
##  Nesting 
`atomic()` blocks can be nested. Inner blocks use PostgreSQL savepoints, so a failure in an inner block rolls back only that block's changes (not the entire outer transaction), provided you catch the exception:
    ```python
    from django.db.transaction import atomic
    with atomic():
        Specialty.objects.create(name="Cardiology")
        try:
            with atomic():
                Specialty.objects.create(name="Neurology")
                raise ValueError("something went wrong")
        except ValueError:
            pass  # Only the "Neurology" insert is rolled back
        # "Cardiology" is still pending and will be committed
    ```
If the exception is **not** caught, it propagates to the outer block and rolls back everything.
* * *
##  See Also 
  - [CustomModels](/sdk/custom-data-custom-models/) \- Defining structured models, relationships, and queries
  - [AttributeHubs](/sdk/custom-data-attribute-hubs/) \- Standalone key-value storage
  - [Design Considerations](/sdk/custom-data-design-considerations/) \- Choosing the right technique and avoiding anti-patterns
  - [Testing Custom Data](/sdk/custom-data-testing/) \- Testing utilities and examples
----- END PAGE https://docs.canvasmedical.com/sdk/custom-data-transactions/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/custom-data/
##  Overview 
The Canvas SDK provides two techniques for storing custom data in your plugins, allowing you to define fully structured data models with relationships among entities, or create flexible key-value stores:
  1. **[CustomModels](/sdk/custom-data-custom-models/)** \- Build your own data model or expand the Canvas data model by adding fully structured tables with typed fields, relationships, and indexes
  2. **[AttributeHubs](/sdk/custom-data-attribute-hubs/)** \- Store arbitrary key-value pairs and JSON data independently of the Canvas data model
Each technique serves different use cases and provides different levels of structure and type safety. Both techniques may be used together.
##  When to Use Each Technique 
###  CustomModels 
Use this when you need structured, typed data with relationships and normalized data. CustomModels can also extend existing SDK models (like Patient or Staff) with custom fields via `OneToOneField`, `ForeignKey`, and `ManyToManyField`.
**Best for:**
  - Structured data with a stable, known schema
  - Custom fields on existing SDK models (e.g., provider preferences, patient flags)
  - Relationships between entities (foreign keys, join tables)
  - Data requiring compound filtering, sorting, or aggregation
  - Data consumed by reports or analytics
**Example use cases:**
  - Provider specialties and certifications
  - Adding practice-specific fields to patients or staff
  - Linking `Staff` to `Note` creating a `supervising_provider` association
  - Custom workflows and forms
  - Integration-specific data structures
  - Practice-specific business operation concepts and logic
[Learn more about CustomModels →](/sdk/custom-data-custom-models/)
###  AttributeHubs 
AttributeHubs provide a key/value and document store free from the burden of defining any schema or linking to Canvas models. They are for storing irregular or unstructured information that doesn't have a natural home. Whereas CustomModels build upon the Canvas data model, AttributeHubs allow easy, standalone persistence of information. Use this when you need to store data that doesn't naturally belong to any existing or imagined model.
**Best for:**
  - Cross-cutting state that spans multiple models (sync cursors, external IDs)
  - One-off or small-collection configuration and state
  - Data with no natural schema (varying fields per record)
  - External system state tracking
**Example use cases:**
  - API synchronization state
  - External system identifiers
  - Plugin configuration and feature flags
[Learn more about AttributeHubs →](/sdk/custom-data-attribute-hubs/)
For help choosing between these techniques, see [Design Considerations](/sdk/custom-data-design-considerations/). For details on how multiple plugins can share a namespace using these keys, see the [Sharing Data](/sdk/custom-data-sharing-data/) guide. For managing API tokens and other sensitive configuration, see [Managing Secrets](/sdk/secrets/).
##  Caching 
If your use case represents transient data that should expire via TTL, use the [Caching API](/sdk/caching) instead of the Custom Data features.
##  Data Privacy and Plugin Isolation 
All custom data created by a plugin — whether using CustomModels or AttributeHubs — is scoped to a namespace. This isolation ensures that plugins cannot directly access or modify another plugin's data, maintaining security and data integrity across the system.
Plugins may share data in two ways:
  - By explicit co-location within a namespace, allowing direct database access
  - By publishing [Simple API](/sdk/handlers-simple-api-http) endpoints
[Learn more about data sharing](/sdk/custom-data-sharing-data)
###  Data Isolation 
**CustomModels** created by a plugin exist within namespaces. Tables and data are completely isolated from other namespaces.
    ```python
    # In a plugin named "my_plugin": Creates a table "specialty" in the "my_plugin" namespace
    from canvas_sdk.v1.data.base import CustomModel
    from django.db.models import TextField
    class Specialty(CustomModel):
        name = TextField()
    ```
    ```python
    # In a plugin named "your_plugin": Creates a table "specialty" in the "your_plugin" namespace
    from canvas_sdk.v1.data.base import CustomModel
    from django.db.models import TextField
    class Specialty(CustomModel):
        name = TextField()
    # In "your_plugin": Cannot access the "my_plugin" Specialty model or data
    ```
**AttributeHubs** similarly store data within the plugin's namespace and are not accessible to plugins in other namespaces.
##  Testing Custom Data 
The Canvas SDK provides comprehensive testing utilities for all custom data approaches. See the [Testing Custom Data](/sdk/custom-data-testing/) guide for detailed examples and best practices.
##  Sharing Data 
Use APIs to make data available and accessible to and from other plugins and external services. See the [Sharing Data](/sdk/custom-data-sharing-data/) guide for detailed examples and best practices.
##  Read Replica Databases 
All the data managed by plugin is available via the database read replica. To access it, alter the PostgreSQL [search_path](https://www.postgresql.org/docs/18/ddl-schemas.html#DDL-SCHEMAS-PATH) to include the namespaces that you intend to query.
##  Limitations (for Safety) 
  - Values stored in `text` and `json` fields may not exceed 1mb as measured by character count.
  - Bulk operations (e.g., `bulk_create`) are limited to 10,000 records at a time.
##  See Also 
  - [CustomModels](/sdk/custom-data-custom-models/) \- Structured models with relationships among entities
  - [Extending SDK Models](/sdk/custom-data-extending-sdk-models/) \- Proxy models, `related_name` namespacing, and referencing SDK models
  - [AttributeHubs](/sdk/custom-data-attribute-hubs/) \- Standalone key-value storage
  - [Design Considerations](/sdk/custom-data-design-considerations/) \- Choosing the right technique and avoiding anti-patterns
  - [Transactions](/sdk/custom-data-transactions/) \- All-or-nothing writes with `transaction.atomic()`
  - [Testing Custom Data](/sdk/custom-data-testing/) \- Testing utilities and examples
  - [Sharing Data](/sdk/custom-data-sharing-data/) \- Sharing data with other plugins and external services
  - [Data Models](/sdk/data/) \- Core SDK data models
  - [Caching API](/sdk/caching) \- Auto-expiring transient data
  - [Simple API](/sdk/handlers-simple-api/) \- Simple API for sharing data between plugins
  - [Secrets](/sdk/secrets/) \- Managing API keys and sensitive configuration
----- END PAGE https://docs.canvasmedical.com/sdk/custom-data/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-allergy-intolerance/
##  Introduction 
The `AllergyIntolerance` model represents a known risk, specific to a patient, of a harmful or undesirable physiological response associated with exposure to a substance.
##  Basic usage 
To get an allergy intolerance by identifier, use the `get` method on the `AllergyIntolerance` model manager:
    ```python
    from canvas_sdk.v1.data.allergy_intolerance import AllergyIntolerance
    allergy = AllergyIntolerance.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    ```
If you have a patient object, the allergy intolerances for a patient can be accessed with the `allergy_intolerances` attribute on a `Patient` object:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    allergies = patient.allergy_intolerances.all()
    ```
If you have a patient ID, you can get the allergies for the patient with the `for_patient` method on the `AllergyIntolerance` model manager:
    ```python
    from canvas_sdk.v1.data.allergy_intolerance import AllergyIntolerance
    patient_id = "1eed3ea2a8d546a1b681a2a45de1d790"
    allergies = AllergyIntolerance.objects.for_patient(patient_id)
    ```
##  Codings 
The codings for an allergy intolerance can be accessed with the `codings` attribute on an `AllergyIntolerance` object:
    ```python
    from canvas_sdk.v1.data.allergy_intolerance import AllergyIntolerance
    from logger import log
    allergy = AllergyIntolerance.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    for coding in allergy.codings.all():
        log.info(f"system:  {coding.system}")
        log.info(f"code:    {coding.code}")
        log.info(f"display: {coding.display}")
    ```
##  Filtering 
Allergy intolerances can be filtered by any attribute that exists on the model.
Filtering for allergy intolerances is done with the `filter` method on the `AllergyIntolerance` model manager.
###  By attribute 
Specify an attribute with `filter` to filter by that attribute:
    ```python
    from canvas_sdk.v1.data.allergy_intolerance import AllergyIntolerance
    allergies = AllergyIntolerance.objects.filter(status="active")
    ```
###  By ValueSet 
See [Value Sets](/sdk/data-value-sets/) for the library of built-in value sets and how to create your own.
Filtering by ValueSet works a little differently. The `find` method on the model manager is used to perform `ValueSet` filtering:
    ```python
    from canvas_sdk.v1.data.allergy_intolerance import AllergyIntolerance
    from canvas_sdk.value_set.v2022.allergy import EggSubstance
    allergies = AllergyIntolerance.objects.find(EggSubstance)
    ```
##  Attributes 
###  AllergyIntolerance 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
patient | [Patient](/sdk/data-patient/#patient)  
note_id | Integer  
allergy_intolerance_type | String  
category | Integer  
status | String  
severity | String  
onset_date | Date  
onset_date_original_input | String  
last_occurrence | Date  
last_occurrence_original_input | String  
recorded_date | DateTime  
narrative | String  
codings | AllergyIntoleranceCoding[]  
remove_allergy_events | [RemoveAllergyEvent](/sdk/data-remove-allergy-event/#removeallergyevent)[]  
###  AllergyIntoleranceCoding 
Field Name | Type  
---|---  
dbid | Integer  
system | String  
version | String  
code | String  
display | String  
user_selected | Boolean  
allergy_intolerance | AllergyIntolerance  
----- END PAGE https://docs.canvasmedical.com/sdk/data-allergy-intolerance/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-application/
##  Introduction 
The `Application` model represents a plugin Application in Canvas. Applications are used to integrate third-party tools and services into the Canvas platform, allowing users to access external resources and functionalities directly from within Canvas. Each application has a unique identifier, name and description.
##  Basic usage 
To get an application by identifier, use the `get` method on the `Application` model manager:
    ```python
    from canvas_sdk.v1.data import Application
    application = Application.objects.get(identifier="123")
    ```
##  Filtering 
Applications can be filtered by any attribute that exists on the model.
Filtering for applications is done with the `filter` method on the `Application` model manager.
###  By attribute 
Specify an attribute with `filter` to filter by that attribute:
    ```python
    from canvas_sdk.v1.data import Application
    applications = Application.objects.filter(name="application name")
    ```
##  Attributes 
###  Application 
Field Name | Type  
---|---  
identifier | str  
name | str  
description | str
----- END PAGE https://docs.canvasmedical.com/sdk/data-application/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-appointment/
##  Introduction 
The `Appointment` model represents a single scheduled meeting from a patient, that may be in the future or past.
##  Basic usage 
To get an appointment by identifier, use the `get` method on the `Appointment` model manager:
    ```python
    from canvas_sdk.v1.data.appointment import Appointment
    appointment = Appointment.objects.get(id="f53626e4-0683-43ac-a1b7-c52815639ce2")
    ```
If you have a patient object, the appointments for a patient can be accessed with the `appointments` attribute on a `Patient` object:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    appointments = patient.appointments.all()
    ```
To get appointments part of a recurrence.
    ```python
    from canvas_sdk.v1.data.appointment import Appointment
    appointment = Appointment.objects.get(id="f53626e4-0683-43ac-a1b7-c52815639ce2")
    # parent appointment
    parent_appointment = appointment.parent_appointment
    # children appointments
    children = parent_appointment.children.all()
    ```
##  Filtering 
Appointments can be filtered by any attribute that exists on the model.
Filtering for appointments is done with the `filter` method on the `Appointment` model manager.
###  By attribute 
Specify an attribute with `filter` to filter by that attribute:
    ```python
    from canvas_sdk.v1.data.appointment import Appointment, AppointmentProgressStatus
    appointments = Appointment.objects.filter(status=AppointmentProgressStatus.CONFIRMED)
    ```
###  Filtering by External Identifiers 
To query Appointments by external identifiers, the `external_identifiers` relation can be used with double-underscores to identify values stored on the AppointmentExternalIdentifier model. For example:
    ```python
    from canvas_sdk.v1.data.appointment import Appointment
    appointment = Appointment.objects.filter(
        external_identifiers__system="COMPANY_IDENTIFIER",
        external_identifiers__value="ejNoTa5vKzoT9oSjg87MVB").first()
    ```
##  Attributes 
###  Appointment 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
patient | [Patient](/sdk/data-patient/#patient)  
appointment_rescheduled_from | Appointment  
parent_appointment | Appointment  
provider | Staff  
start_time | DateTime  
duration_minutes | Integer  
comment | String  
note_id | Integer  
note_type_id | Integer  
status | String  
meeting_link | URL  
telehealth_instructions_sent | Boolean  
location | PracticeLocation  
description | String  
external_identifiers | AppointmentExternalIdentifier[]  
metadata | AppointmentMetadata[]  
children | Appointment[]  
appointment_rescheduled_to | Appointment[]  
labels | [TaskLabel](/sdk/data-task/#tasklabel)[]  
###  AppointmentExternalIdentifier 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
use | String  
identifier_type | String  
system | String  
value | String  
issued_date | Date  
expiration_date | Date  
appointment | Appointment  
###  AppointmentMetadata 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
appointment | Appointment  
key | String  
value | String  
    ```python
    from canvas_sdk.v1.data.appointment import Appointment
    from logger import log
    appointment_id = "f53626e4-0683-43ac-a1b7-c52815639ce2"
    appointment = Appointment.objects.get(id=appointment_id)
    appointment_metadata = appointment.metadata.all()
    for metadata in appointment_metadata:
       log.info(f"Appointment metadata: {metadata.key}, {metadata.value}")
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/data-appointment/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-assessment/
##  Introduction 
The `Assessment` model represents a clinical assessment or evaluation of a patient's medical `Condition`.
##  Basic usage 
To get an assessment by identifier, use the `get` method on the `Assessment` model manager:
    ```python
    from canvas_sdk.v1.data.assessment import Assessment
    assessment = Assessment.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    ```
If you have a patient object, the assessments for a patient can be accessed with the `assessments` attribute on a `Patient` object:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    assessments = patient.assessments.all()
    ```
##  Filtering 
Assessments can be filtered by any attribute that exists on the model.
Filtering for assessments is done with the `filter` method on the `Assessment` model manager.
###  By attribute 
Specify an attribute with `filter` to filter by that attribute:
    ```python
    from canvas_sdk.v1.data.assessment import Assessment, AssessmentStatus
    assessments = Assessment.objects.filter(patient__id="1eed3ea2a8d546a1b681a2a45de1d790", status=AssessmentStatus.STATUS_IMPROVING)
    ```
###  Committed assessments 
The `committed` method returns assessments that have been committed and not entered in error:
    ```python
    from canvas_sdk.v1.data.assessment import Assessment
    committed_assessments = Assessment.objects.committed()
    ```
##  Attributes 
###  Assessment 
Field Name | Type |   
---|---|---  
id | UUID |   
dbid | Integer |   
created | DateTime |   
modified | DateTime |   
originator | [CanvasUser](/sdk/data-canvasuser) |   
entered_in_error | [CanvasUser](/sdk/data-canvasuser) |   
committer | [CanvasUser](/sdk/data-canvasuser) |   
patient | [Patient](/sdk/data-patient/#patient) |   
note | [Note](/sdk/data-note/#note) |   
condition | [Condition](/sdk/data-condition/#condition) |   
interview | [Interview](/sdk/data-questionnaire/#interview) |   
status | AssessmentStatus |   
narrative | String |   
background | String |   
care_team | String |   
treatments_stated | [MedicationStatement](/sdk/data-medication-statement)[] |   
billinglineitem_set | [BillingLineItem](/sdk/data-billing-line-item)[] |   
referrals | [Referral](/sdk/data-referral)[] |   
##  Enumeration types 
###  Assessment Status 
Value | Label  
---|---  
STATUS_IMPROVING | Improved  
STATUS_STABLE | Unchanged  
STATUS_DETERIORATING | Deteriorated  
----- END PAGE https://docs.canvasmedical.com/sdk/data-assessment/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-banner-alert/
##  Introduction 
The `BannerAlert` model represents alerts associated with [Patient](/sdk/data-patient/#patient) records. This page deals with data retrieval. To create or remove `BannerAlert` records, see [Banner Alert Effects](/sdk/effect-banner-alerts/).
##  Usage 
The `BannerAlert` model can be used to find all of the banner alert records linked to a patient. For example, to find all of the banner alerts for a patient, the `Patient.banner_alerts` method can be used:
    ```python
    >>> from canvas_sdk.v1.data.patient import Patient
    >>> patient_1 = Patient.objects.get(id="aebe4d3f5d18410388dc69c4b5169fc3")
    >>> patient_banner_alerts = patient_1.banner_alerts.all()
    >>> print([item.narrative for item in patient_banner_alerts])
    ['Patient spits when angry', 'Confirm contact info']
    ```
##  Filtering 
The `filter` method can be used to filter by desired attributes. The following examples show commonly used operations to filter banner alert data:
**Show a Patient's active BannerAlert records from the 'foo' plugin in order of descending creation date**
    ```python
    >>> from canvas_sdk.v1.data.patient import Patient
    >>> patient_1 = Patient.objects.get(id="aebe4d3f5d18410388dc69c4b5169fc3")
    >>> banner_alerts = patient_1.banner_alerts.filter(status='active', plugin_name='foo').order_by("created")
    >>> print([item.narrative for item in banner_alerts])
    ['foo', 'bar']
    ```
##  Attributes 
###  BannerAlert 
Field Name | Type  
---|---  
dbid | Integer  
id | UUID  
created | DateTime  
modified | DateTime  
patient | [Patient](/sdk/data-patient/#patient)  
plugin_name | String  
key | String  
narrative | String  
placement | BannerAlertPlacement[]  
intent | BannerAlertIntent  
href | String  
status | BannerAlertStatus  
##  Enumeration types 
###  BannerAlertStatus 
Value | Label  
---|---  
active | Active  
inactive | Inactive  
###  BannerAlertIntent 
Value | Label  
---|---  
info | Info  
warning | Warning  
alert | Alert  
###  BannerAlertPlacement 
Value | Label  
---|---  
chart | Chart  
timeline | Timeline  
appointment_card | Appointment Card  
scheduling_card | Scheduling Card  
profile | Profile
----- END PAGE https://docs.canvasmedical.com/sdk/data-banner-alert/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-billing-line-item/
##  Introduction 
The `BillingLineItem` model represents billing line items linked to [Notes](/sdk/data-note) that can be found in the note footer. BillingLineItems are also linked to [Patient](/sdk/data-patient/#patient) instances.
##  Usage 
The `BillingLineItem` model can be used to find all of the billable codes linked to a patient note. For example, to find all of the current billing line items for a note, the `Note.billing_line_items` method can be used:
    ```python
    >>> from canvas_sdk.v1.data.note import Note
    >>> from canvas_sdk.v1.data.billing import BillingLineItemStatus
    >>> note_1 = Note.objects.get(id="a74592ae8a6c4d0ebe0799d3fb3713d1")
    >>> note_1_billing_line_items = note_1.billing_line_items.filter(status=BillingLineItemStatus.ACTIVE)
    >>> print([item.cpt for item in note_1_billing_line_items])
    ['99213', '90703']
    ```
Alternatively, you could find all the `BillingLineItem` instances for a single `Patient`:
    ```python
    >>> from canvas_sdk.v1.data.patient import Patient
    >>> patient_1 = Patient.objects.get(id="aebe4d3f5d18410388dc69c4b5169fc3")
    >>> patient_billing_line_items = patient_1.billing_line_items.all()
    >>> print([item.cpt for item in patient_billing_line_items])
    ['99213', '90703', '76942', '67505']
    ```
You can also access all `BillingLineItemModifier`s associated with a `BillingLineItem`:
    ```python
    >>> from canvas_sdk.v1.data.billing import BillingLineItem, BillingLineItemModifier
    >>> line_item = BillingLineItem.objects.get(id="b80b1cdc2e6a4aca90ccebc02e683f35")
    >>> line_item_modifiers = line_item.modifiers.all()
    >>> print([mod.code for mod in line_item_modifiers])
    ['25', '59']
    ```
##  Filtering 
The `filter` method can be used to filter by desired attributes. The following examples show commonly used operations to filter billing line item data:
**Show a Patient's active BillingLineItems that start with '99-' in order of descending charge amount**
    ```python
    >>> from canvas_sdk.v1.data.patient import Patient
    >>> from canvas_sdk.v1.data.billing import BillingLineItemStatus
    >>> patient_1 = Patient.objects.get(id="aebe4d3f5d18410388dc69c4b5169fc3")
    >>> office_visit_charges = patient_1.billing_line_items.filter(status=BillingLineItemStatus.ACTIVE, cpt__startswith='99').order_by("charge")
    >>> print([(item.cpt, item.charge,) for item in office_visit_charges])
    [('99215', 200.00), ('99215', 190.00), ('99214', 100.00), ('99213', 80.00)]
    ```
**Find All Removed BillingLineItems from a Note**
    ```python
    >>> import arrow
    >>> from canvas_sdk.v1.data.note import Note
    >>> from canvas_sdk.v1.data.billing import BillingLineItemStatus
    >>> note_1 = Note.objects.get(id="a74592ae8a6c4d0ebe0799d3fb3713d1")
    >>> note_1_removed_items = note_1.billing_line_items.filter(status=BillingLineItemStatus.REMOVED)
    >>> print([item.cpt for item in note_1_removed_items])
    ['11901', '00950']
    ```
For examples of how to use the BillingLineItem data class with the BillingLineItem effects, check out [this page](/sdk/effect-billing-line-items)
##  Attributes 
###  BillingLineItem 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
note | [Note](/sdk/data-note)  
patient | [Patient](/sdk/data-patient/#patient)  
cpt | String  
charge | Decimal  
description | String  
units | Integer  
command_type | String  
command_id | Integer  
status | BillingLineItemStatus  
assessments | [Assessment](/sdk/data-assessment)[]  
modifiers | BillingLineItemModifier[]  
claimlineitem_set | [ClaimLineItem](/sdk/data-claim/#claimlineitem)[]  
###  BillingLineItemModifier 
Field Name | Type  
---|---  
dbid | Integer  
line_item | BillingLineItem  
system | String  
version | String  
code | String  
display | String  
user_selected | Boolean  
##  Enumeration types 
###  BillingLineItemStatus 
Value | Label  
---|---  
ACTIVE | Active  
REMOVED | Removed
----- END PAGE https://docs.canvasmedical.com/sdk/data-billing-line-item/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-business-line/
##  Introduction 
The `BusinessLine` model represents a group of [Patients](/sdk/data-patient/#patient) that share a common brand under an [Organization](/sdk/data-organization).
##  Usage 
The `BusinessLine` model can be used to find all of the patients for a given Business Line:
    ```python
    >>> from canvas_sdk.v1.data import BusinessLine
    >>> business_line = BusinessLine.objects.get(id="ff844d60Od18466698dc645PtYZ3019Tt")
    >>> business_line_patients = business_line.patients.all()
    >>> print([patient.first_name for patient in business_line_patients])
    ['George', 'Louise', 'Julia']
    ```
You can also access a patient's Business Line from the `Patient` model:
    ```python
    >>> from canvas_sdk.v1.data import Patient
    >>> patient_1 = Patient.objects.get(id="aebe4d3f5d18410388dc69c4b5169fc3")
    >>> patient_business_line = patient_1.business_line
    >>> print(patient_business_line.name)
    'New Patients that love cheese'
    ```
And you can also access all of the Business Lines under a given Organization:
    ```python
    >>> from canvas_sdk.v1.data import Organization
    >>> organization = Organization.objects.first()
    >>> organization_business_lines = organization.business_lines.all()
    >>> print([business_line.name for business_line in organization_business_lines])
    ['New Patients that love cheese', 'Spanish Speaking Patients', 'One Medical']
    ```
##  Filtering 
The `filter` method can be used to filter by desired attributes. The following examples show commonly used operations to filter Business Line data:
**Show an Organization's Business Lines that are active and in the 732 area code**
    ```python
    >>> from canvas_sdk.v1.data import BusinessLine, Organization
    >>> org = Organization.objects.first()
    >>> active_732_business_lines = BusinessLine.objects.filter(organization=org, active=True, area_code="732")
    >>> print([business_line.name for business_line in active_732_business_lines])
    ['Foo', 'Bar']
    ```
##  Attributes 
###  BusinessLine 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
name | String  
description | String  
area_code | String  
subdomain | String  
active | Boolean  
state | BusinessLineState  
organization | [Organization](/sdk/data-organization)  
patients | QuerySet[[Patient](/sdk/data-patient/#patient)]  
##  Enumeration types 
###  BusinessLineState 
Value | Label  
---|---  
success | Success  
pending | Pending  
error | Deleted
----- END PAGE https://docs.canvasmedical.com/sdk/data-business-line/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-calendar/
##  Introduction 
The `Calendar` model represents a Calendar in Canvas. Calendars are used to organize events for providers and can be either Clinic or Administrative type.
##  Basic usage 
To get a calendar by identifier, use the `get` method on the `Calendar` model manager:
    ```python
    from canvas_sdk.v1.data.calendar import Calendar
    calendar = Calendar.objects.get(id="f53626e4-0683-43ac-a1b7-c52815639ce2")
    ```
##  Events 
The events associated with a calendar can be accessed with the `events` attribute on a `Calendar` object:
    ```python
    from canvas_sdk.v1.data.calendar import Calendar
    calendar = Calendar.objects.get(id="f53626e4-0683-43ac-a1b7-c52815639ce2")
    events = calendar.events.all()
    ```
To get a specific event by identifier:
    ```python
    from canvas_sdk.v1.data.calendar import Event
    event = Event.objects.get(id="a1b2c3d4-5678-90ab-cdef-1234567890ab")
    ```
##  Filtering 
Calendars and events can be filtered by any attribute that exists on the model.
Filtering is done with the `filter` method on the model manager.
###  By attribute 
Specify an attribute with `filter` to filter by that attribute:
    ```python
    from canvas_sdk.v1.data.calendar import Calendar
    # Filter calendars by title
    calendars = Calendar.objects.filter(title__icontains="Clinic")
    ```
###  By calendar name 
To filter calendars by a specific provider name, calendar type, and location, use the `for_calendar_name` method:
    ```python
    from canvas_sdk.v1.data.calendar import Calendar
    calendars = Calendar.objects.for_calendar_name(
        provider_name="Dr. Smith",
        calendar_type="Clinic",
        location="Main Office"
    )
    ```
##  Attributes 
###  Calendar 
Field Name | Type | Description  
---|---|---  
id | UUID | Unique identifier for the calendar  
dbid | Integer | Database identifier  
title | String | The title of the calendar  
timezone | TimeZone | The timezone for the calendar (default: UTC)  
description | String | Optional description of the calendar's purpose  
events | Event[] | Events associated with this calendar  
###  Event 
Field Name | Type | Description  
---|---|---  
id | UUID | Unique identifier for the event  
dbid | Integer | Database identifier  
title | String | The title of the event  
description | String | Description of the event  
calendar | Calendar | The calendar this event belongs to  
starts_at | DateTime | The start date and time of the event  
ends_at | DateTime | The end date and time of the event  
recurrence | String | Recurrence rule for recurring events  
recurrence_ends_at | DateTime | The date and time when the recurrence pattern ends  
recurring_parent_event | Event | The parent event  
exceptions | Event[] | Exception (override) events for this recurring parent event  
original_starts_at | DateTime | The original start time for recurring event exceptions  
is_all_day | Boolean | Whether this is an all-day event (default: false)  
is_cancelled | Boolean | Whether this event has been cancelled (default: false)  
allowed_note_types | NoteType[] | Note types that are allowed for this event  
##  Examples 
###  Working with calendar events 
    ```python
    from canvas_sdk.v1.data.calendar import Calendar
    from datetime import datetime
    from logger import log
    calendar = Calendar.objects.get(id="f53626e4-0683-43ac-a1b7-c52815639ce2")
    # Get all upcoming events
    upcoming_events = calendar.events.filter(
        starts_at__gte=datetime.now(),
        is_cancelled=False
    ).order_by('starts_at')
    for event in upcoming_events:
        log.info(f"Event: {event.title}")
        log.info(f"Starts at: {event.starts_at}")
        log.info(f"Ends at: {event.ends_at}")
        if event.recurrence:
            log.info(f"Recurrence: {event.recurrence}")
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/data-calendar/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-cancel-prescription-response/
##  Introduction 
The `CancelPrescriptionResponse` model captures the response to a [CancelPrescription](/sdk/data-cancel-prescription) request. Each response is linked one-to-one to the request that produced it.
##  Basic usage 
To get a cancel prescription response by identifier, use the `get` method on the `CancelPrescriptionResponse` model manager:
    ```python
    from canvas_sdk.v1.data import CancelPrescriptionResponse
    response = CancelPrescriptionResponse.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    ```
If you have a patient object, the cancel prescription responses for a patient can be accessed with the `cancel_prescription_responses` attribute:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    responses = patient.cancel_prescription_responses.all()
    ```
Or, from a cancel prescription, reach its response with the `response` attribute:
    ```python
    from canvas_sdk.v1.data import CancelPrescription
    cancel = CancelPrescription.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    response = cancel.response
    ```
##  Attributes 
###  CancelPrescriptionResponse 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
patient | [Patient](/sdk/data-patient/#patient)  
request | [CancelPrescription](/sdk/data-cancel-prescription)  
message_id | String  
note | String  
reason_code | String  
response | String  
----- END PAGE https://docs.canvasmedical.com/sdk/data-cancel-prescription-response/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-cancel-prescription/
##  Introduction 
The `CancelPrescription` model is the anchor for the CancelPrescription command — a request to cancel a patient's prescription, recorded on a Note.
##  Basic usage 
To get a cancel prescription by identifier, use the `get` method on the `CancelPrescription` model manager:
    ```python
    from canvas_sdk.v1.data import CancelPrescription
    cancel = CancelPrescription.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    ```
If you have a patient object, the cancel prescriptions for a patient can be accessed with the `cancel_prescriptions` attribute:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    cancels = patient.cancel_prescriptions.all()
    ```
Or, from a prescription, reach its cancellations with the same `cancel_prescriptions` attribute:
    ```python
    from canvas_sdk.v1.data import Prescription
    prescription = Prescription.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    cancels = prescription.cancel_prescriptions.all()
    ```
##  Committed records 
The `committed` method returns cancel prescriptions that have been committed and not entered in error:
    ```python
    from canvas_sdk.v1.data import CancelPrescription
    committed_cancels = CancelPrescription.objects.committed()
    ```
##  Attributes 
###  CancelPrescription 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
originator | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
patient | [Patient](/sdk/data-patient/#patient)  
note | [Note](/sdk/data-note)  
prescription | [Prescription](/sdk/data-prescription)  
message_id | String  
status | CancelPrescriptionStatus  
response | [CancelPrescriptionResponse](/sdk/data-cancel-prescription-response/#cancelprescriptionresponse)  
##  Enumeration types 
###  CancelPrescriptionStatus 
Name | Value  
---|---  
OPEN | open  
PENDING | pending  
ULTIMATELY_ACCEPTED | ultimately-accepted  
----- END PAGE https://docs.canvasmedical.com/sdk/data-cancel-prescription/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-canvasuser/
##  Introduction 
The `CanvasUser` model represents a Canvas User. This could be linked to a staff member or a patient. This model isn't meant to be referenced directly, but is sometimes used to attribute a record to user.
##  Basic usage 
To get a user by identifier, use the `get` method on the `CanvasUser` model manager:
    ```python
    from canvas_sdk.v1.data import CanvasUser
    user = CanvasUser.objects.get(dbid=123)
    ```
##  Filtering 
Users can be filtered by any attribute that exists on the model.
Filtering for users is done with the `filter` method on the `CanvasUser` model manager.
###  By attribute 
Specify attributes with `filter` to filter by those attributes:
    ```python
    from canvas_sdk.v1.data import CanvasUser
    users = CanvasUser.objects.filter(phone_number="1111111111", email="test@canvasmedical.com")
    ```
##  Attributes 
###  User 
Field Name | Type  
---|---  
dbid | Integer  
email | String  
phone_number | String  
is_staff | Boolean  
is_portal_registered | Boolean  
last_invite_date_time | DateTime  
person_subclass | [Staff](/sdk/data-staff/#staff) | [Patient](/sdk/data-patient/#patient)  
staff | [Staff](/sdk/data-staff/#staff)  
patient | [Patient](/sdk/data-patient/#patient)  
sent_messages | [Message](/sdk/data-message/#message)[]  
received_messages | [Message](/sdk/data-message/#message)[]  
commands_originated | [Command](/sdk/data-command/#command)[]  
commands_committed | [Command](/sdk/data-command/#command)[]  
commands_entered_in_error | [Command](/sdk/data-command/#command)[]  
----- END PAGE https://docs.canvasmedical.com/sdk/data-canvasuser/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-care-team/
##  Introduction 
The `CareTeam` model represents a collection of [Staff](/sdk/data-staff/#staff) that are responsible for the care of a [Patient](/sdk/data-patient/#patient).
##  Usage 
There are 2 data models associated with Care Teams - `CareTeamMembership` and `CareTeamRole`. The `CareTeamRole` model stores all of the available roles that are available to be filled by staff members (i.e. _Physician_ , _Nurse Practitioner_ , etc.). For example, the following code will show the names of active roles that are available in a Canvas instance:
    ```python
    >>> from canvas_sdk.v1.data.care_team import CareTeamRole
    >>> active_care_team_roles = CareTeamRole.objects.filter(active=True)
    >>> role_names = [role.display for role in active_care_team_roles]
    >>> print(role_names)
    ['Primary care physician', 'Physician', 'Physician assistant', 'Nurse practitioner', 'Health coach', 'Care coordinator']
    ```
The `CareTeamMembership` model connects patients, staff members and their associated roles to make up the assembly of a patient's Care Team. To retrieve staff members and their respective roles on a patient's care team, the `care_team_memberships` attribute available on a [Patient](/sdk/data-patient/#patient) instance can be used:
    ```python
    >>> from canvas_sdk.v1.data.patient import Patient
    >>> patient_1 = Patient.objects.get(id="a74592ae8a6c4d0ebe0799d3fb3713d1")
    >>> patient_1_care_team = patient_1.care_team_memberships.all()
    >>> print([(ctm.role.display, ctm.staff,) for ctm in patient_1_care_team])
    [('Primary care physician', <Staff: Steven Magee>), ('Nurse practitioner', <Staff: Annalies Hines>), ('Physician assistant', <Staff: Erik McDonald>)]
    ```
###  External care team members 
A care team can also include external members — providers who are not [Staff](/sdk/data-staff#staff) on the Canvas instance. An external membership has no `staff`. Instead, its `organizational_entity` links to an [OrganizationalEntity](/sdk/data-organizational-entity/#organizationalentity) that describes the external provider. When that entity is a [ServiceProvider](/sdk/data-serviceprovider/#service-provider), the membership's `service_provider` property resolves directly to it, so you can read the provider's contact details — such as `business_fax` — without leaving the plugin.
    ```python
    >>> from canvas_sdk.v1.data.patient import Patient
    >>> patient_1 = Patient.objects.get(id="a74592ae8a6c4d0ebe0799d3fb3713d1")
    >>> external_member = patient_1.care_team_memberships.filter(staff__isnull=True).first()
    >>> external_member.service_provider.business_fax
    '18005551234'
    ```
The `service_provider` property returns `None` for internal (staff-backed) memberships, and for external members whose organizational entity is not a `Service Provider`.
##  Filtering 
The `filter` method can be used to filter by desired attributes. The following examples show commonly used operations to filter care team data:
**Find a patient's care team lead**
    ```python
    >>> from canvas_sdk.v1.data.patient import Patient
    >>> from canvas_sdk.v1.data.care_team import CareTeamMembershipStatus
    >>> patient_1 = Patient.objects.get(id="a74592ae8a6c4d0ebe0799d3fb3713d1")
    >>> patient_1_care_team_lead = patient_1.care_team_memberships.filter(lead=True, status=CareTeamMembershipStatus.ACTIVE).first()
    >>> assert patient_1_care_team_lead is not None
    >>> print((patient_1_care_team_lead.staff, patient_1_care_team_lead.role,))
    (<Staff: Steven Magee>, <CareTeamRole: Primary care physician>)
    ```
**Find all Patients that have a Certain Staff Member on their Care Team**
    ```python
    >>> from canvas_sdk.v1.data.staff import Staff
    >>> from canvas_sdk.v1.data.care_team import CareTeamMembershipStatus
    >>> staff_member = Staff.objects.get(id="3640cd20de8a470aa570a852859ac87e")
    >>> staff_care_teams = staff_member.care_team_memberships.filter(status=CareTeamMembershipStatus.ACTIVE)
    >>> print([(ctm.patient, ctm.lead,) for ctm in staff_care_teams])
    [(<Patient: Danny Boy>, True), (<Patient: Sally Mae>, False)]
    ```
##  Attributes 
###  CareTeamRole 
Field Name | Type  
---|---  
dbid | Integer  
system | String  
version | String  
code | String  
display | String  
user_selected | BooleanField  
active | Boolean  
care_teams | [CareTeamMembership](/sdk/data-care-team/#careteammembership)[]  
###  CareTeamMembership 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
patient | [Patient](/sdk/data-patient/#patient)  
staff | [Staff](/sdk/data-staff#staff)  
role | CareTeamRole  
organizational_entity | [OrganizationalEntity](/sdk/data-organizational-entity/#organizationalentity)  
status | CareTeamMembershipStatus  
lead | Boolean  
role_code | String  
role_system | String  
role_display | String  
For external (non-staff) members, `staff` is empty and `organizational_entity` links to the external provider.
####  Properties 
Name | Type | Description  
---|---|---  
service_provider | [ServiceProvider](/sdk/data-serviceprovider/#service-provider) | `None` | The external provider for this membership, resolved through its `organizational_entity`; `None` for internal members.  
##  Enumeration types 
###  CareTeamMembershipStatus 
Value | Label  
---|---  
proposed | Proposed  
active | Active  
suspended | Suspended  
inactive | Inactive  
entered-in-error | Entered in Error
----- END PAGE https://docs.canvasmedical.com/sdk/data-care-team/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-change-medication/
##  Introduction 
The `ChangeMedication` model represents a record of a Change Medication command, used to update the directions (sig) of a medication already on a patient's medication list without issuing a new prescription.
##  Basic usage 
To get a change medication by identifier, use the `get` method on the `ChangeMedication` model manager:
    ```python
    from canvas_sdk.v1.data import ChangeMedication
    change_medication = ChangeMedication.objects.get(id="61a1853f-168f-4ed3-80d2-44e5d144bcf3")
    ```
If you have a patient object, the change medications for a patient can be accessed with the `change_medications` attribute on a `Patient` object:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    change_medications = patient.change_medications.all()
    ```
You can also access the referenced medication with the `medication` attribute:
    ```python
    from canvas_sdk.v1.data import ChangeMedication
    change_medication = ChangeMedication.objects.get(id="61a1853f-168f-4ed3-80d2-44e5d144bcf3")
    medication = change_medication.medication
    ```
Or for a given medication, you can access all change medication records:
    ```python
    from canvas_sdk.v1.data import Medication
    medication = Medication.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    change_medications = medication.change_medications.all()
    ```
##  Committed records 
The `committed` method returns change medications that have been committed and not entered in error:
    ```python
    from canvas_sdk.v1.data import ChangeMedication
    committed_change_medications = ChangeMedication.objects.committed()
    ```
##  Attributes 
###  ChangeMedication 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
patient | [Patient](/sdk/data-patient/#patient)  
note | [Note](/sdk/data-note)  
medication | [Medication](/sdk/data-medication)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
originator | [CanvasUser](/sdk/data-canvasuser)  
created | DateTime  
modified | DateTime  
sig_original_input | String  
----- END PAGE https://docs.canvasmedical.com/sdk/data-change-medication/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-charge-description-master/
##  Introduction 
The `ChargeDescriptionMaster` model represents a billing charge in Canvas that can be added to the note footer.
##  Usage 
The `ChargeDescriptionMaster` model can be filtered by any of its attributes, including `cpt_code`, `name`, and `short_name`:
    ```python
    >>> from canvas_sdk.v1.data import ChargeDescriptionMaster
    >>> office_visit_charges = ChargeDescriptionMaster.objects.filter(cpt_code__startswith="99")
    >>> print([charge.short_name for charge in office_visit_charges])
    ["Office outpatient visit 40 minutes", "Office outpatient visit 25 minutes", "Office outpatient visit 10 minutes"]
    ```
You can also access `PayorSpecificCharge`s from the `ChargeDescriptionMaster` model:
    ```python
    >>> from canvas_sdk.v1.data import ChargeDescriptionMaster
    >>> office_visit_40min = ChargeDescriptionMaster.objects.filter(cpt_code="99215").first()
    >>> payor_specific_charges = office_visit_40min.transactor_charges.all()
    >>> print([charge.transactor.name for charge in payor_specific_charges])
    ["Aetna", "Medicare", "Blue Shield of CA"]
    ```
`
##  Attributes 
###  ChargeDescriptionMaster 
Field Name | Type  
---|---  
dbid | Integer  
cpt_code | String  
name | String  
short_name | String  
charge_amount | Decimal  
effective_date | Date  
end_date | Date  
code_system | CDMCodeSystem  
ndc_code | String  
transactor_charges | QuerySet[[PayorSpecificCharge](/sdk/data-payor-specific-charge/#payorspecificcharge)]  
vaccines | QuerySet[[Vaccine](/sdk/data-vaccine/#vaccine)]  
##  Enumeration types 
###  CDMCodeSystem 
Value | Label  
---|---  
INTERNAL | Internal  
CPT | CPT
----- END PAGE https://docs.canvasmedical.com/sdk/data-charge-description-master/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-chart-section-review/
##  Introduction 
The `ChartSectionReview` model represents a reviewed chart section captured on a note, with its pre-rendered content. When a provider reviews a chart section during a visit, Canvas stores a snapshot of that section's content at the time of review.
##  Basic usage 
To get a chart section review by identifier, use the `get` method on the `ChartSectionReview` model manager:
    ```python
    from canvas_sdk.v1.data.chart_section_review import ChartSectionReview
    review = ChartSectionReview.objects.get(id="b5a0c1d2-e3f4-5678-9abc-def012345678")
    ```
If you have a patient object, the chart section reviews for a patient can be accessed with the `chart_section_reviews` attribute on a `Patient` object:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    reviews = patient.chart_section_reviews.all()
    ```
If you have a note object, the chart section reviews for that note can be accessed with the `chart_section_reviews` attribute on a `Note` object:
    ```python
    from canvas_sdk.v1.data.note import Note
    note = Note.objects.get(id="89992c23-c298-4118-864a-26cb3e1ae822")
    reviews = note.chart_section_reviews.all()
    ```
##  Filtering 
Chart section reviews can be filtered by any attribute that exists on the model.
###  By attribute 
Specify an attribute with `filter` to filter by that attribute:
    ```python
    from canvas_sdk.v1.data.chart_section_review import (
        ChartSectionReview,
        ChartSectionReviewSection,
    )
    # Get all reviews for the conditions section
    condition_reviews = ChartSectionReview.objects.filter(
        section=ChartSectionReviewSection.CONDITIONS
    )
    ```
###  By patient and section 
    ```python
    from canvas_sdk.v1.data.chart_section_review import (
        ChartSectionReview,
        ChartSectionReviewSection,
    )
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    medication_reviews = ChartSectionReview.objects.filter(
        patient=patient,
        section=ChartSectionReviewSection.MEDICATIONS
    )
    ```
###  Committed reviews 
The `committed` method returns chart section reviews that have been committed and not entered in error:
    ```python
    from canvas_sdk.v1.data.chart_section_review import ChartSectionReview
    committed_reviews = ChartSectionReview.objects.committed()
    ```
##  Working with entries 
`entries` is a list of integer `dbid` values identifying the records that were reviewed in the section. Which model those `dbid`s belong to depends on the review's `section`:
`section` | Model(s) referenced by `entries`  
---|---  
`conditions` | [Condition](/sdk/data-condition/) (non-surgical)  
`surgical_history` | [Condition](/sdk/data-condition/) (surgical)  
`medications` | [Medication](/sdk/data-medication/)  
`family_histories` | [FamilyHistory](/sdk/data-family-history/#familyhistory)  
`allergies` | [AllergyIntolerance](/sdk/data-allergy-intolerance/)  
`immunizations` | [Immunization](/sdk/data-immunization/) and [ImmunizationStatement](/sdk/data-immunization/)  
If you only need to **see what was reviewed** , prefer the `content` field: it holds the pre-rendered text of the reviewed records (one line per entry, captured at review time), so it needs no entry resolution and avoids the ambiguity described below.
To work with the **record objects themselves** , filter the corresponding model by `dbid__in=review.entries`. Always scope the query to `review.patient` as well: a `dbid` is unique only within its own table, so scoping by patient avoids matching an unrelated record that happens to share the same integer (this is also why the `immunizations` section, which draws from two models, is resolved against both).
    ```python
    from canvas_sdk.v1.data.allergy_intolerance import AllergyIntolerance
    from canvas_sdk.v1.data.chart_section_review import (
        ChartSectionReview,
        ChartSectionReviewSection,
    )
    from canvas_sdk.v1.data.condition import Condition
    from canvas_sdk.v1.data.family_history import FamilyHistory
    from canvas_sdk.v1.data.immunization import Immunization, ImmunizationStatement
    from canvas_sdk.v1.data.medication import Medication
    review = ChartSectionReview.objects.get(id="b5a0c1d2-e3f4-5678-9abc-def012345678")
    if review.section == ChartSectionReviewSection.CONDITIONS:
        records = Condition.objects.filter(
            patient=review.patient, dbid__in=review.entries, surgical=False
        )
    elif review.section == ChartSectionReviewSection.SURGICAL_HISTORY:
        records = Condition.objects.filter(
            patient=review.patient, dbid__in=review.entries, surgical=True
        )
    elif review.section == ChartSectionReviewSection.MEDICATIONS:
        records = Medication.objects.filter(patient=review.patient, dbid__in=review.entries)
    elif review.section == ChartSectionReviewSection.FAMILY_HISTORIES:
        records = FamilyHistory.objects.filter(patient=review.patient, dbid__in=review.entries)
    elif review.section == ChartSectionReviewSection.ALLERGIES:
        records = AllergyIntolerance.objects.filter(patient=review.patient, dbid__in=review.entries)
    elif review.section == ChartSectionReviewSection.IMMUNIZATIONS:
        # entries may reference either model, so resolve against both.
        records = [
            *Immunization.objects.filter(patient=review.patient, dbid__in=review.entries),
            *ImmunizationStatement.objects.filter(patient=review.patient, dbid__in=review.entries),
        ]
    ```
> **Note on the`immunizations` section:** `entries` stores bare `dbid` integers with no indication of which model each one came from. Because `Immunization` and `ImmunizationStatement` are separate tables with independent `dbid` sequences, the same integer can be a valid `dbid` in both. If a patient has an `Immunization` and an `ImmunizationStatement` that share a `dbid` and only one was reviewed, resolving against both models (as above) will return both records — there is no way to disambiguate them from `entries` alone. Treat the immunizations result as a best-effort superset, not an exact match. If you only need to know what was reviewed, use `content` instead: it captured the rendered text of the actual reviewed items at review time.
##  Attributes 
###  ChartSectionReview 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
patient | [Patient](/sdk/data-patient/#patient)  
note | [Note](/sdk/data-note/#note)  
section | ChartSectionReviewSection  
entries | Integer[] (`dbid`s of the reviewed records — see Working with entries)  
content | String (newline-separated bullet items)  
originator | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
##  Enumeration types 
###  ChartSectionReviewSection 
Value | Label  
---|---  
conditions | Conditions  
surgical_history | Surgical History  
medications | Medications  
family_histories | Family Histories  
allergies | Allergies  
immunizations | Immunizations
----- END PAGE https://docs.canvasmedical.com/sdk/data-chart-section-review/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-claim/
##  Introduction 
This module defines the data models used to manage healthcare claim workflows.
##  Basic usage 
To retrieve a claim by its identifier:
    ```python
    from canvas_sdk.v1.data.claim import Claim
    claim = Claim.objects.get(id="9d2e0f58-338b-11ec-8d3d-0242ac130003")
    ```
To access diagnosis codes for a claim:
    ```python
    from canvas_sdk.v1.data.claim import Claim
    claim = Claim.objects.get(id="9d2e0f58-338b-11ec-8d3d-0242ac130003")
    diagnosis_codes = claim.diagnosis_codes.all().order_by("rank")
    for diagnosis in diagnosis_codes:
        print(f"Rank {diagnosis.rank}: {diagnosis.code} - {diagnosis.display}")
    ```
To access banner alerts for a claim:
    ```python
    from canvas_sdk.v1.data.claim import Claim
    claim = Claim.objects.get(id="9d2e0f58-338b-11ec-8d3d-0242ac130003")
    active_alerts = claim.banner_alerts.filter(status="active")
    for alert in active_alerts:
        print(f"[{alert.intent}] {alert.narrative}")
    ```
##  Filtering 
    ```python
    from canvas_sdk.v1.data.claim import Claim
    # Active claims only
    active_claims = Claim.objects.active()
    ```
##  Attributes 
###  Claim 
Represents a complete healthcare claim. Claim belongs to a Note and has a one-to-one relationship with a ClaimPatient.
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
note | [Note](/sdk/data-note/)  
installment_plan | InstallmentPlan  
current_queue | ClaimQueue  
current_coverage | ClaimCoverage  
accept_assign | Boolean  
auto_accident | Boolean  
auto_accident_state | String  
employment_related | Boolean  
other_accident | Boolean  
accident_code | String  
illness_date | Date  
remote_batch_id | String  
remote_file_id | String  
prior_auth | String  
narrative | String  
account_number | String  
snoozed_until | Date  
patient_balance | Decimal  
aggregate_coverage_balance | Decimal  
created | DateTime  
modified | DateTime  
diagnosis_codes | ClaimDiagnosisCode[]  
comments | ClaimComment[]  
line_items | ClaimLineItem[]  
labels | [TaskLabel](/sdk/data-task/#tasklabel)[]  
metadata | ClaimMetadata[]  
banner_alerts | ClaimBannerAlert[]  
provider | ClaimProvider  
incident_to | Boolean  
supervising_provider | ClaimSupervisingProvider  
latest_invoice | [Invoice](/sdk/data-invoice/#invoice)  
patient | ClaimPatient  
coverages | ClaimCoverage[]  
submissions | ClaimSubmission[]  
postings | [BasePosting](/sdk/data-posting/#baseposting)[]  
**Computed Properties** :
  - `total_charges`: Total charges for active line items
  - `total_paid`: Sum of paid amounts from postings
  - `total_adjusted`: Sum of adjustments and transfers
  - `balance`: Remaining balance (coverage + patient)
  - `total_patient_paid`: Paid amount by the patient
  - `total_payer_paid`: Paid amount by coverages
**Helpful Methods** :
  - `get_coverage_by_payer_id(payer_id: str, subscriber_number: str | None = None)`: Finds the active coverage associated with a payer_id. Optionally checks if the subscriber_number matches, which will choose the correct coverage in the case where a patient has two coverages with the same payer_id.
###  ClaimSupervisingProvider 
An immutable snapshot of a claim's supervising provider (837P loop 2310D), captured at claim creation from the note's supervising provider and frozen thereafter, so later edits to the note or the underlying Staff record do not retroactively change a submitted claim.
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
claim | Claim  
staff | [Staff](/sdk/data-staff/#staff)  
first_name | String  
last_name | String  
middle_name | String  
npi | String  
taxonomy | String  
tax_id | String  
tax_id_type | [TaxIDType](/sdk/data-enumeration-types/#taxidtype)  
created | DateTime  
modified | DateTime  
###  ClaimLineItem 
Represents individual billed procedures or services tied to a claim.
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
billing_line_item | [BillingLineItem](/sdk/data-billing-line-item/)  
diagnosis_codes | ClaimLineItemDiagnosisCode[]  
modifiers | ClaimLineItemModifier[]  
claim | Claim  
status | ClaimLineItemStatus  
charge | Decimal  
from_date | String  
thru_date | String  
narrative | String  
ndc_code | String  
ndc_dosage | String  
ndc_measure | String  
place_of_service | [PracticeLocationPOS](/sdk/data-note/#practicelocationpos)  
proc_code | String  
display | String  
remote_chg_id | String  
units | Integer  
epsdt | String  
family_planning | FamilyPlanningOptions  
created | DateTime  
modified | DateTime  
###  ClaimLineItemDiagnosisCode 
Represents a diagnosis code for a given ClaimLineItem. There exists one ClaimLineItemDiagnosisCode for each ClaimDiagnosisCode, and the "linked" attribute indicates whether or not the diagnosis code is linked to the line item.
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
line_item | ClaimLineItem  
claim_diagnosis_code | ClaimDiagnosisCode  
code | String  
poa | String  
linked | Boolean  
created | DateTime  
modified | DateTime  
###  ClaimLineItemModifier 
Represents a modifier code for a given ClaimLineItem.
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
line_item | ClaimLineItem  
modifier | String  
created | DateTime  
modified | DateTime  
###  ClaimCoverage 
Links a claim to a specific insurance coverage.
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
claim | Claim  
coverage | [Coverage](/sdk/data-coverage/)  
active | Boolean  
payer_name | String  
payer_id | String  
payer_typecode | String  
payer_order | ClaimPayerOrder  
payer_addr1 | String  
payer_addr2 | String  
payer_city | String  
payer_state | String  
payer_zip | String  
payer_plan_type | ClaimTypeCode  
coverage_type | [CoverageType](/sdk/data-coverage/#coveragetype)  
subscriber_employer | String  
subscriber_group | String  
subscriber_number | String  
subscriber_plan | String  
subscriber_dob | String  
subscriber_first_name | String  
subscriber_last_name | String  
subscriber_middle_name | String  
subscriber_phone | String  
subscriber_sex | [PersonSex](/sdk/data-patient/#sexatbirth)  
subscriber_addr1 | String  
subscriber_addr2 | String  
subscriber_city | String  
subscriber_state | String  
subscriber_zip | String  
subscriber_country | String  
patient_relationship_to_subscriber | [CoverageRelationshipCode](/sdk/data-coverage/#coveragerelationshipcode)  
pay_to_addr1 | String  
pay_to_addr2 | String  
pay_to_city | String  
pay_to_state | String  
pay_to_zip | String  
resubmission_code | String  
payer_icn | String  
created | DateTime  
modified | DateTime  
###  ClaimComment 
Represents a free-text comment made on a Claim.
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
claim | Claim  
created | DateTime  
modified | DateTime  
originator | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
comment | String  
###  ClaimBannerAlert 
Represents banner alerts associated with a claim. Banner alerts are displayed in the UI to surface important information about a claim. To create or remove `ClaimBannerAlert` records, see [Claim Effects](/sdk/effect-claims/#add-banner).
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
claim | Claim  
plugin_name | String  
key | String  
narrative | String  
intent | [BannerAlertIntent](/sdk/data-banner-alert/#banneralertintent)  
href | String  
status | [BannerAlertStatus](/sdk/data-banner-alert/#banneralertstatus)  
created | DateTime  
modified | DateTime  
###  ClaimDiagnosisCode 
Represents diagnosis codes associated with a claim, ordered by rank.
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
claim | Claim  
line_item_diagnosis_codes | ClaimLineItemDiagnosisCode[]  
rank | Integer  
code | String  
display | String  
created | DateTime  
modified | DateTime  
###  ClaimQueue 
Defines the metadata for claim queues used in revenue workflows.
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
queue_sort_ordering | Integer  
name | String  
display_name | String  
description | String  
show_in_revenue | Boolean  
visible_columns | Array[ClaimQueueColumns]  
created | DateTime  
modified | DateTime  
###  ClaimPatient 
Captures patient-level data related to a specific claim.
Field Name | Type  
---|---  
dbid | Integer  
claim | Claim  
photo | String  
dob | String  
first_name | String  
last_name | String  
middle_name | String  
phone | String  
sex | [PersonSex](/sdk/data-patient/#sexatbirth)  
ssn | String  
addr1 | String  
addr2 | String  
city | String  
state | String  
zip | String  
country | String  
created | DateTime  
modified | DateTime  
###  ClaimLabel 
Represents labels assigned to the claim.
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
claim | Claim  
label | [TaskLabel](/sdk/data-task/#tasklabel)  
###  ClaimMetadata 
Represents key-value metadata associated with a claim. Each claim-key pair is unique.
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
claim | Claim  
key | String  
value | String  
created | DateTime  
modified | DateTime  
###  ClaimProvider 
Captures provider-level data related to a specific claim.
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
claim | Claim  
clia_number | String  
billing_provider_name | String  
billing_provider_phone | String  
billing_provider_addr1 | String  
billing_provider_addr2 | String  
billing_provider_city | String  
billing_provider_state | String  
billing_provider_zip | String  
billing_provider_id | String  
billing_provider_npi | String  
billing_provider_tax_id | String  
billing_provider_tax_id_type | String  
billing_provider_taxonomy | String  
provider_id | String  
provider_first_name | String  
provider_last_name | String  
provider_middle_name | String  
provider_npi | String  
provider_tax_id | String  
provider_tax_id_type | String  
provider_taxonomy | String  
provider_ptan_identifier | String  
provider_addr1 | String  
provider_addr2 | String  
provider_city | String  
provider_state | String  
provider_zip | String  
referring_provider_id | String  
referring_provider_first_name | String  
referring_provider_last_name | String  
referring_provider_middle_name | String  
referring_provider_npi | String  
referring_provider_ptan_identifier | String  
ordering_provider_first_name | String  
ordering_provider_last_name | String  
ordering_provider_middle_name | String  
ordering_provider_npi | String  
facility_id | String  
facility_name | String  
facility_npi | String  
facility_addr1 | String  
facility_addr2 | String  
facility_city | String  
facility_state | String  
facility_zip | String  
hosp_from_date | String  
hosp_to_date | String  
created | DateTime  
modified | DateTime  
###  ClaimSubmission 
Captures clearinghouse submission details about a claim.
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
claim | Claim  
coverage | ClaimCoverage  
clearinghouse_claim_id | String  
claim_index | Integer  
###  InstallmentPlan 
Represents a payment plan between a patient and provider.
Field Name | Type  
---|---  
dbid | Integer  
creator | [CanvasUser](/sdk/data-canvasuser/)  
patient | [Patient](/sdk/data-patient/)  
total_amount | Decimal  
status | InstallmentPlanStatus  
expected_payoff_date | Date  
created | DateTime  
modified | DateTime  
claims | Claim[]  
##  Enumeration types 
###  ClaimLineItemStatus 
Value | Label  
---|---  
active | Active  
removed | Removed  
###  LineItemCodes 
Value  
---  
COPAY  
UNLINKED  
###  FamilyPlanningOptions 
Value | Label  
---|---  
Y | Yes  
N | No  
###  ClaimLineItemStatus 
Value | Label  
---|---  
active | Active  
removed | Removed  
###  LineItemCodes 
Value  
---  
COPAY  
UNLINKED  
###  FamilyPlanningOptions 
Value | Label  
---|---  
Y | Yes  
N | No  
###  ClaimPayerOrder 
Value | Label  
---|---  
Primary | Primary  
Secondary | Secondary  
Tertiary | Tertiary  
Quaternary | Quaternary  
Quinary | Quinary  
###  ClaimTypeCode 
Code | Description  
---|---  
12 | Working Aged (Age 65 or older)  
13 | End-Stage Renal Disease  
14 | No-fault  
15 | Workers Compensation  
41 | Black Lung  
42 | Veterans Administration  
43 | Disabled (Under Age 65)  
47 | Other Liability Insurance is primary  
"" | No Typecode necessary  
###  ClaimQueueColumns 
Value | Label  
---|---  
NoteType | Note type  
ClaimID | Claim ID  
DateOfService | Date of service  
Patient | Patient  
ActiveInsurance | Active insurance  
InsuranceBalance | Insurance balance  
PatientBalance | Patient balance  
DaysInQueue | Days in queue  
Provider | Provider  
Guarantor | Guarantor  
LatestRemit | Latest remit  
LastInvoiced | Last invoiced  
SnoozedUntil | Snoozed until  
Labels | Labels  
###  ClaimQueues 
Value | Label  
---|---  
1 | Appointment  
2 | NeedsClinicianReview  
3 | NeedsCodingReview  
4 | QueuedForSubmission  
5 | FiledAwaitingResponse  
6 | RejectedNeedsReview  
7 | AdjudicatedOpenBalance  
8 | PatientBalance  
9 | ZeroBalance  
10 | Trash  
###  InstallmentPlanStatus 
Value | Label  
---|---  
active | Active  
completed | Completed  
cancelled | Cancelled
----- END PAGE https://docs.canvasmedical.com/sdk/data-claim/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-command/
##  Introduction 
The `Command` model represents a [command](/sdk/commands/) in a note.
##  Basic usage 
To get a command by identifier, use the `get` method on the `Command` model manager:
    ```python
    from canvas_sdk.v1.data.command import Command
    command = Command.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    ```
##  Filtering 
Commands can be filtered by any attribute that exists on the model.
Filtering for commands is done with the `filter` method on the `Command` model manager.
###  By attribute 
Specify an attribute with `filter` to filter by that attribute:
    ```python
    from canvas_sdk.v1.data.command import Command
    commands = Command.objects.filter(state="committed")
    ```
##  Command types and data 
When events are fired as part of [Command Lifecycle Events](/sdk/events/#command-lifecycle-events), the `self.target` value that is available within a plugin will contain the `id` value of the command. For example:
    ```python
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.effects import Effect
    from canvas_sdk.events import EventType
    from logger import log
    class MyHandler(BaseHandler):
        RESPONDS_TO = [
            EventType.Name(EventType.REASON_FOR_VISIT_COMMAND__POST_UPDATE),
        ]
        def compute(self) -> list[Effect]:
            log.info(self.target) # logs the Command id
    ```
Using this value, the `Command` model can be queried to fetch additional data about the command. Two main fields to pay attention to here are the `schema_key` and `data` fields. The `schema_key` field contains the type of the command, while the `data` field contains a JSON object with command data as key/value pairs:
    ```python
    import json
    from canvas_sdk.effects import Effect
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.v1.data.command import Command
    from logger import log
    class MyHandler(BaseHandler):
        def compute(self) -> list[Effect]:
            command_instance = Command.objects.get(id=self.target)
            log.info(command_instance.schema_key)
            log.info(json.dumps(command_instance.data, indent=2))
    ```
For example, for a _Reason For Visit_ command, the preceding code would log the following lines:
    ```sh
    reasonForVisit
    {
      "coding": {
        "text": "Accident-prone",
        "extra": null,
        "value": "165002",
        "disabled": false,
        "annotations": null,
        "description": null
      },
      "comment": "Patient would like to discuss condition."
    }
    ```
The following table shows the different command `schema_key` values with links to their respective [Command Modules](/sdk/commands). The attributes shown in each corresponding entry contain the structure that will appear in the `data` JSON field of each `Command`.
Schema Key | Command Data  
---|---  
adjustPrescription | [AdjustPrescription](/sdk/commands/#adjustprescription)  
allergy | [Allergy](/sdk/commands/#allergy)  
assess | [Assess](/sdk/commands/#assess)  
changeMedication | [ChangeMedication](/sdk/commands/#changemedication)  
closeGoal | [CloseGoal](/sdk/commands/#closegoal)  
diagnose | [Diagnose](/sdk/commands/#diagnose)  
familyHistory | [FamilyHistory](/sdk/commands/#familyhistory)  
followUp | [FollowUp](/sdk/commands/#followup)  
goal | [Goal](/sdk/commands/#goal)  
hpi | [HistoryOfPresentIllness](/sdk/commands/#historyofpresentillness)  
imagingOrder | [ImagingOrder](/sdk/commands/#imagingorder)  
instruct | [Instruct](/sdk/commands/#instruct)  
labOrder | [LabOrder](/sdk/commands/#laborder)  
medicalHistory | [MedicalHistory](/sdk/commands/#medicalhistory)  
medicationStatement | [MedicationStatement](/sdk/commands/#medicationstatement)  
perform | [Perform](/sdk/commands/#perform)  
plan | [Plan](/sdk/commands/#plan)  
pocLabTest | [POCLabTest](/sdk/commands/#poclabtest)  
prescribe | [Prescribe](/sdk/commands/#prescribe)  
questionnaire | [Questionnaire](/sdk/commands/#questionnaire)  
reasonForVisit | [ReasonForVisit](/sdk/commands/#reasonforvisit)  
refer | [Refer](/sdk/commands/#refer)  
refill | [Refill](/sdk/commands/#refill)  
removeAllergy | [RemoveAllergy](/sdk/commands/#removeallergy)  
resolveCondition | [ResolveCondition](/sdk/commands/#resolve-condition)  
stopMedication | [StopMedication](/sdk/commands/#stopmedication)  
surgicalHistory | [SurgicalHistory](/sdk/commands/#surgicalhistory)  
task | [Task](/sdk/commands/#task)  
updateDiagnosis | [UpdateDiagnosis](/sdk/commands/#updatediagnosis)  
updateGoal | [UpdateGoal](/sdk/commands/#updategoal)  
vitals | [Vitals](/sdk/commands/#vitals)  
**PLEASE NOTE** the Commands Module is under development and Canvas is working to migrate all commands to be available. This means that some commands are not able to emit events available in plugins, and historical commands created prior to their Commands Module availability may not be able to be queried using the data module. [This product updates table](/product-updates/commands-module/) shows the commands and their release statuses. If a command in a chart is not available by querying the `Command` data model, the data is still available to be queried using corresponding data models (i.e. [Questionnaire](/sdk/data-questionnaire/), [ImagingOrder](/sdk/data-imaging/), etc.).
##  Attributes 
###  Command 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
originator | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
state | String  
patient | [Patient](/sdk/data-patient/#patient)  
note | [Note](/sdk/data-note/#note)  
schema_key | String  
data | JSON  
origination_source | String  
custom_html | String (optional)  
anchor_object_type | String  
anchor_object_dbid | Integer  
anchor_object | Model (optional)  
metadata | QuerySet[[CommandMetadata](/sdk/data-command/#commandmetadata)]  
The `custom_html` field stores HTML content that is rendered alongside the command in the note. This field is optional and defaults to `None`. Use the [`set_custom_html`](/sdk/commands/#set_custom_html) method to set or clear this field on a staged command.
###  CommandMetadata 
`CommandMetadata` stores custom key-value pairs associated with a command. Metadata can be upserted using the `upsert_metadata` method on any command effect class — see [CommandMetadata Effect](/sdk/effect-command-metadata/) for full details.
    ```python
    from canvas_sdk.v1.data.command import CommandMetadata
    # Get all metadata for a command
    metadata_entries = CommandMetadata.objects.filter(command__id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    # Get a specific metadata value
    entry = CommandMetadata.objects.get(command__id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35", key="my_plugin:priority")
    print(entry.value)
    ```
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
command | [Command](/sdk/data-command/#command)  
key | String  
value | String  
----- END PAGE https://docs.canvasmedical.com/sdk/data-command/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-compound-medication/
##  Introduction 
The `CompoundMedication` model represents a compound medication formulation that can be prescribed to patients. Compound medications are customized medications mixed or prepared by a compounding pharmacy according to a prescription.
##  Basic usage 
To get a compound medication by identifier, use the `get` method on the `CompoundMedication` model manager:
    ```python
    from canvas_sdk.v1.data.compound_medication import CompoundMedication
    compound_medication = CompoundMedication.objects.get(id="123")
    ```
##  Filtering 
Compound medications can be filtered by any attribute that exists on the model.
Filtering for compound medications is done with the `filter` method on the `CompoundMedication` model manager.
###  By attribute 
Specify attributes with `filter` to filter by those attributes:
    ```python
    from canvas_sdk.v1.data.compound_medication import CompoundMedication
    # Get all active compound medications
    active_medications = CompoundMedication.objects.filter(active=True)
    # Get compound medications by formulation
    compound_medications = CompoundMedication.objects.filter(formulation="Testosterone 200mg/mL in Grapeseed Oil")
    # Get all Schedule II controlled substances
    schedule_ii_medications = CompoundMedication.objects.filter(controlled_substance="II")
    # Get compound medications by potency unit
    tablet_medications = CompoundMedication.objects.filter(potency_unit_code="C48542")
    ```
###  Multiple filters 
You can combine multiple filters:
    ```python
    from canvas_sdk.v1.data.compound_medication import CompoundMedication
    # Get active compound medications that are controlled substances
    controlled_active = CompoundMedication.objects.filter(
      active=True,
      controlled_substance__in=["II", "III", "IV", "V"]
    )
    ```
##  Attributes 
###  CompoundMedication 
Field Name | Type  
---|---  
dbid | Integer  
id | UUID  
active | Boolean  
formulation | String  
potency_unit_code | PotencyUnit  
controlled_substance | ControlledSubstanceSchedule  
controlled_substance_ndc | String  
compound_medication | QuerySet[[Prescription](/sdk/data-prescription/#prescription)]  
##  Enumeration types 
###  PotencyUnit 
Value | Label  
---|---  
C62412 | Applicator  
C54564 | Blister  
C64696 | Caplet  
C48480 | Capsule  
C64933 | Each  
C53499 | Film  
C48155 | Gram  
C69124 | Gum  
C48499 | Implant  
C62276 | Insert  
C48504 | Kit  
C120263 | Lancet  
C48506 | Lozenge  
C28254 | Milliliter  
C48521 | Packet  
C65032 | Pad  
C48524 | Patch  
C120216 | Pen Needle  
C62609 | Ring  
C53502 | Sponge  
C53503 | Stick  
C48538 | Strip  
C48539 | Suppository  
C53504 | Swab  
C48542 | Tablet  
C48548 | Troche  
C38046 | Unspecified  
C48552 | Wafer  
###  ControlledSubstanceSchedule 
Key | Value | Label  
---|---|---  
SCHEDULE_NOT_SCHEDULED | N | None  
SCHEDULE_II | II | Schedule II  
SCHEDULE_III | III | Schedule III  
SCHEDULE_IV | IV | Schedule IV  
SCHEDULE_V | V | Schedule V  
##  Notes 
  - The `formulation` field has a maximum length of 105 characters (as defined by Surescripts).
----- END PAGE https://docs.canvasmedical.com/sdk/data-compound-medication/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-condition/
##  Introduction 
The `Condition` model represents a clinical condition, problem, diagnosis, or other event, situation, issue, or clinical concept that has risen to a level of concern.
##  Basic usage 
To get a condition by identifier, use the `get` method on the `Condition` model manager:
    ```python
    from canvas_sdk.v1.data.condition import Condition
    condition = Condition.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    ```
If you have a patient object, the conditions for a patient can be accessed with the `conditions` attribute on a `Patient` object:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    conditions = patient.conditions.all()
    ```
If you have a patient ID, you can get the conditions for the patient with the `for_patient` method on the `Condition` model manager:
    ```python
    from canvas_sdk.v1.data.condition import Condition
    patient_id = "1eed3ea2a8d546a1b681a2a45de1d790"
    condition = Condition.objects.for_patient(patient_id)
    ```
##  Codings 
The codings for a condition can be accessed with the `codings` attribute on an `Condition` object:
    ```python
    from canvas_sdk.v1.data.condition import Condition
    from logger import log
    condition = Condition.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    for coding in condition.codings.all():
        log.info(f"system:  {coding.system}")
        log.info(f"code:    {coding.code}")
        log.info(f"display: {coding.display}")
    ```
##  Filtering 
Conditions can be filtered by any attribute that exists on the model.
Filtering for conditions is done with the `filter` method on the `Condition` model manager.
###  By attribute 
Specify an attribute with `filter` to filter by that attribute:
    ```python
    from canvas_sdk.v1.data.condition import Condition
    conditions = Condition.objects.filter(onset_date__gte="2024-10-15")
    ```
###  By ValueSet 
See [Value Sets](/sdk/data-value-sets/) for the library of built-in value sets and how to create your own.
Filtering by ValueSet works a little differently. The `find` method on the model manager is used to perform `ValueSet` filtering:
    ```python
    from canvas_sdk.v1.data.condition import Condition
    from canvas_sdk.value_set.v2022.condition import Diabetes
    conditions = Condition.objects.find(Diabetes)
    ```
##  Attributes 
###  Condition 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
patient | [Patient](/sdk/data-patient/#patient)  
onset_date | Date  
resolution_date | Date  
clinical_status | ClinicalStatus  
codings | ConditionCoding[]  
lab_order_reason_conditions | [LabOrderReasonConditionCoding](/sdk/data-labs/#laborderreasoncondition)[]  
notes | String  
surgical | Boolean  
assessments | [Assessment](/sdk/data-assessment/#assessment)[]  
resolutions | [ResolveConditionEvent](/sdk/data-resolve-condition-event/#resolveconditionevent)[]  
###  ConditionCoding 
Field Name | Type  
---|---  
dbid | Integer  
system | String  
version | String  
code | String  
display | String  
user_selected | Boolean  
condition | Condition  
##  Enumeration types 
###  ClinicalStatus 
Value | Label  
---|---  
active | active  
relapse | relapse  
remission | remission  
resolved | resolved  
investigative | investigative  
----- END PAGE https://docs.canvasmedical.com/sdk/data-condition/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-content-type/
##  Introduction 
The `ContentType` model exposes Django content types. Use it to resolve the content type id for a given model, which is required when working with generic relations (such as [document references](/sdk/data-document-reference)) and when generating permalinks.
A content type is identified by two **stable** values — `app_label` and `model` — that are the same on every Canvas instance. Its `dbid` (the content type id) is a per-database auto-increment that **is not stable across environments**. Always resolve the `dbid` at runtime from the `app_label` and `model`; never hardcode a content type id, or it will point at the wrong model in another environment.
##  Basic usage 
To get a content type by its database id, use the `get` method on the `ContentType` model manager:
    ```python
    from canvas_sdk.v1.data import ContentType
    content_type = ContentType.objects.get(dbid=42)
    ```
##  Resolving a content type at runtime 
Because the `dbid` differs per environment, look the content type up by its stable `app_label` and `model`, then read `dbid` from the result:
    ```python
    from canvas_sdk.v1.data import ContentType
    content_type = ContentType.objects.filter(app_label="api", model="note").first()
    if content_type:
        # Resolved for this environment — safe to use for a generic relation or permalink.
        content_type_id = content_type.dbid
    ```
##  Filtering 
Content types can be filtered by any attribute that exists on the model.
Filtering for content types is done with the `filter` method on the `ContentType` model manager.
###  By model 
To find the content type for a specific model, filter by `app_label` and `model`:
    ```python
    from canvas_sdk.v1.data import ContentType
    content_type = ContentType.objects.filter(app_label="api", model="note").first()
    if content_type:
        print(f"Content type id: {content_type.dbid}")
    ```
##  app_label and model for data module models 
Use these stable values to resolve a content type with `ContentType.objects.filter(app_label=..., model=...)`. The `model` value is the lowercased Django model name, and most data module models live under the `api` app. This list is not exhaustive — any model not shown here can be resolved the same way once you know its `app_label` and `model`.
###  `api` app 
SDK data model | app_label | model  
---|---|---  
[AllergyIntolerance](/sdk/data-allergy-intolerance/) | `api` | `allergyintolerance`  
[Appointment](/sdk/data-appointment/) | `api` | `appointment`  
[Assessment](/sdk/data-assessment/) | `api` | `assessment`  
[BannerAlert](/sdk/data-banner-alert/) | `api` | `banneralert`  
[ChartSectionReview](/sdk/data-chart-section-review/) | `api` | `chartsectionreview`  
[Condition](/sdk/data-condition/) | `api` | `condition`  
[Coverage](/sdk/data-coverage/) | `api` | `coverage`  
[DetectedIssue](/sdk/data-detected-issue/) | `api` | `detectedissue`  
[Device](/sdk/data-device/) | `api` | `device`  
[DiagnosticReport](/sdk/data-labs/#diagnosticreport) | `api` | `diagnosticreport`  
[DocumentReference](/sdk/data-document-reference/) | `api` | `documentreference`  
[Encounter](/sdk/data-encounter/) | `api` | `encounter`  
[Facility](/sdk/data-facility/) | `api` | `facility`  
[Goal](/sdk/data-goal/) | `api` | `goal`  
[ImagingOrder](/sdk/data-imaging/) | `api` | `imagingorder`  
[ImagingReport](/sdk/data-imaging/) | `api` | `imagingreport`  
[ImagingReview](/sdk/data-imaging/) | `api` | `imagingreview`  
[Immunization](/sdk/data-immunization/) | `api` | `immunization`  
[Instruction](/sdk/data-instruction/) | `api` | `instruction`  
[Interview](/sdk/data-questionnaire/) | `api` | `interview`  
[LabOrder](/sdk/data-labs/) | `api` | `laborder`  
[LabReport](/sdk/data-labs/) | `api` | `labreport`  
[LabValue](/sdk/data-labs/) | `api` | `labvalue`  
[Letter](/sdk/data-letter/) | `api` | `letter`  
[Medication](/sdk/data-medication/) | `api` | `medication`  
[MedicationStatement](/sdk/data-medication-statement/) | `api` | `medicationstatement`  
[Message](/sdk/data-message/) | `api` | `message`  
[Note](/sdk/data-note/) | `api` | `note`  
[Observation](/sdk/data-observation/) | `api` | `observation`  
[Organization](/sdk/data-organization/) | `api` | `organization`  
[OrganizationalEntity](/sdk/data-organizational-entity/) | `api` | `organizationalentity`  
[Patient](/sdk/data-patient/) | `api` | `patient`  
[PatientConsent](/sdk/data-patient-consent/) | `api` | `patientconsent`  
[PatientGroup](/sdk/data-patient-group/) | `api` | `patientgroup`  
[PracticeLocation](/sdk/data-practicelocation/) | `api` | `practicelocation`  
[Prescription](/sdk/data-prescription/) | `api` | `prescription`  
[Questionnaire](/sdk/data-questionnaire/) | `api` | `questionnaire`  
[ReasonForVisit](/sdk/data-reason-for-visit/) | `api` | `reasonforvisit`  
[Referral](/sdk/data-referral/) | `api` | `referral`  
[Staff](/sdk/data-staff/) | `api` | `staff`  
[StopMedicationEvent](/sdk/data-stop-medication-event/) | `api` | `stopmedicationevent`  
[Task](/sdk/data-task/) | `api` | `task`  
[Team](/sdk/data-team/) | `api` | `team`  
[UncategorizedClinicalDocument](/sdk/data-uncategorized-clinical-document/) | `api` | `uncategorizedclinicaldocument`  
[VisualExamFinding](/sdk/data-visual-exam-finding/) | `api` | `visualexamfinding`  
###  Other apps 
Some models live in a different Django app, so their `app_label` is not `api`:
SDK data model | app_label | model  
---|---|---  
[Command](/sdk/data-command/) | `commands` | `command`  
[Application](/sdk/data-application/) | `plugin_io` | `application`  
[PluginCommand](/sdk/data-plugin-command/) | `plugin_io` | `plugincommand`  
[Calendar](/sdk/data-calendar/) | `calendars` | `calendar`  
[ExternalEvent](/sdk/data-external-event/) | `data_integration` | `externalevent`  
[ServiceProvider](/sdk/data-serviceprovider/) | `data_integration` | `serviceprovider`  
[ChargeDescriptionMaster](/sdk/data-charge-description-master/) | `quality_and_revenue` | `chargedescriptionmaster`  
[Claim](/sdk/data-claim/) | `quality_and_revenue` | `claim`  
[PayorSpecificCharge](/sdk/data-payor-specific-charge/) | `quality_and_revenue` | `payorspecificcharge`  
##  Attributes 
###  ContentType 
Field Name | Type  
---|---  
dbid | Integer  
app_label | String  
model | String  
  - **dbid** : The internal database primary key, which is the content type id used for generic relations and permalinks. This value is environment-specific — resolve it at runtime rather than hardcoding it.
  - **app_label** : The label of the application the model belongs to (e.g., `api`).
  - **model** : The lowercased name of the model (e.g., `note`).
----- END PAGE https://docs.canvasmedical.com/sdk/data-content-type/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-coverage/
##  Introduction 
The `Coverage` model represents insurance coverage linked to [Patients](/sdk/data-patient/#patient). Coverages are linked to [Patient](/sdk/data-patient/#patient) instances, as well as `Transactor` instances, which represent the issuer for the corresponding coverage. `Coverage`s also have an associated `EligibilitySummary`, which provides the most up-to-date copay and coinsurance values.
Coverages can also be linked to a [`Snapshot`](/sdk/data-snapshot/#snapshot), which provides access to insurance card images captured via the Canvas iOS application or uploaded through the coverages modal.
##  Usage 
The `Coverage` model can be used to find all of the coverages defined in a Canvas instance, whether overall or for a particular patient. For example, to find all of the current coverages for a patient, the `Patient.coverages` method can be used:
    ```python
    >>> import arrow
    >>> from canvas_sdk.v1.data.patient import Patient
    >>> patient_1 = Patient.objects.get(id="a74592ae8a6c4d0ebe0799d3fb3713d1")
    >>> patient_1_current_coverages = patient_1.coverages.filter(coverage_end_date__gt=arrow.now().date().isoformat())
    >>> print([coverage.issuer.name for coverage in patient_1_current_coverages])
    ['AVALON HEALTHCARE SOLUTIONS CAPITAL BLUE CROSS']
    ```
Alternatively, to find all of the `Coverage` instances issed by a particular issuer/transactor, the `Transactor` model can be queried:
    ```python
    >>> from canvas_sdk.v1.data.coverage import Coverage, Transactor
    >>> transactor_1 = Transactor.objects.get(payer_id="AVA03")
    >>> transactor_coverages = Coverage.objects.filter(issuer=transactor_1)
    >>> print(transactor_coverages)
    <QuerySet [<Coverage: id=89793979-dbff-4a53-b928-75db973c2bdc>, <Coverage: id=423c0f77-8083-4cc1-8e29-2c7d348281e4>]>
    >>>
    ```
Find the latest eligibility summary for a patient:
    ```python
        from canvas_sdk.v1.data.coverage import Coverage, EligibilitySummary
        coverage = Coverage.objects.get(id="a74592ae-8a6c-4d0e-be07-99d3fb3713d1")
        elig_summary_from_model = EligibilitySummary.objects.filter(coverage=coverage).first()
        elig_summary_from_cvg = coverage.eligibility_summary
        if elig_summary_from_model:
            print(elig_summary_from_model.copay_cents, elig_summary_from_model.coinsurance) # 1000 5
        if elig_summary_from_cvg:
            print(elig_summary_from_cvg.copay_cents, elig_summary_from_cvg.coinsurance) # 1000 5
    ```
Access insurance card images through the coverage's snapshot:
    ```python
    from canvas_sdk.v1.data.coverage import Coverage
    coverage = Coverage.objects.get(id="a74592ae-8a6c-4d0e-be07-99d3fb3713d1")
    if coverage.snapshot:
        for image in coverage.snapshot.images.all():
            print(image.image_url)  # Presigned S3 URL for the insurance card image
    ```
##  Eligibility status 
`Coverage.eligibility_status` returns the [`EligibilityResponseStatus`](/sdk/data-eligibility-response/#eligibilityresponsestatus) of the coverage's most recent [`EligibilityResponse`](/sdk/data-eligibility-response/#eligibilityresponse). It returns `UNKNOWN` when the coverage has never been checked (it has no eligibility responses):
    ```python
    from canvas_sdk.v1.data.coverage import Coverage
    from canvas_sdk.v1.data.eligibility_response import EligibilityResponseStatus
    coverage = Coverage.objects.get(id="a74592ae-8a6c-4d0e-be07-99d3fb3713d1")
    if coverage.eligibility_status == EligibilityResponseStatus.ACTIVE:
        print("Coverage is active")
    ```
`Coverage.eligibility_status` returns `NOT_APPLICABLE` for a self-pay coverage, meaning one whose issuer has a `payer_id` of `PATIENT`. It resolves this case before consulting the stored eligibility responses, so a stale `FAILED` response left on a self-pay coverage is never surfaced.
That is also what `Transactor.supports_eligibility_check` reports: it is `False` for the self-pay payer, whose `payer_id` is `PATIENT`, and `True` for every other issuer.
Because it is computed on each access rather than stored, `eligibility_status` cannot be used in `filter()`. Filter on the coverage's [eligibility responses](/sdk/data-eligibility-response/#eligibilityresponse) instead, or read the property once you have the coverage in hand.
A single [`EligibilityResponse.status`](/sdk/data-eligibility-response/#eligibilityresponse), by contrast, never resolves to `UNKNOWN` — that value belongs to the coverage, which has no response to defer to. To react to eligibility changes rather than poll for them, subscribe to the [eligibility response events](/sdk/events/#eligibility-responses). Those fire only when a response is saved, so a coverage that has never been checked emits no event at all: a plugin that has to catch never-verified coverages should read `eligibility_status` rather than rely on the events alone.
##  Filtering 
The `filter` method can be used to filter by desired attributes. The following examples show commonly used operations to filter coverage data:
**Show a Patient's Coverages in order of Rank (Primary, Secondary, etc.)**
    ```python
    >>> from canvas_sdk.v1.data.patient import Patient
    >>> patient_1 = Patient.objects.get(id="aebe4d3f5d18410388dc69c4b5169fc3")
    >>> patient_coverages = patient_1.coverages.all().order_by("coverage_rank")
    >>> print([(coverage.issuer.name, coverage.coverage_rank,) for coverage in patient_coverages])
    [('AVALON HEALTHCARE SOLUTIONS CAPITAL BLUE CROSS', 1), ('Blue Cross Blue Shield of Arizona Advantage', 2)]
    ```
**Find All Expired Coverages**
    ```python
    >>> import arrow
    >>> from canvas_sdk.v1.data.coverage import Coverage
    >>> expired_coverages = Coverage.objects.filter(coverage_end_date__lt=arrow.now().date().isoformat())
    >>> print([f"{coverage.issuer.name} expired {coverage.coverage_end_date.isoformat()}" for coverage in expired_coverages])
    ['Blue Cross Blue Shield of Arizona Advantage expired 2025-01-10']
    ```
##  Attributes 
###  Coverage 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
patient | [Patient](/sdk/data-patient/#patient)  
guarantor | [Patient](/sdk/data-patient/#patient)  
subscriber | [Patient](/sdk/data-patient/#patient)  
subscriber_identifier | String  
patient_relationship_to_subscriber | CoverageRelationshipCode  
issuer | Transactor  
id_number | String  
plan | String  
sub_plan | String  
group | String  
sub_group | String  
employer | String  
coverage_start_date | Date  
coverage_end_date | Date  
coverage_rank | Integer  
state | CoverageState  
plan_type | CoverageType  
coverage_type | TransactorCoverageType  
issuer_address | TransactorAddress  
issuer_phone | TransactorPhone  
comments | Text  
stack | CoverageStack  
snapshot | [Snapshot](/sdk/data-snapshot/#snapshot)  
eligibility_summary | EligibilitySummary  
eligibility_status | [EligibilityResponseStatus](/sdk/data-eligibility-response/#eligibilityresponsestatus) (computed)  
claim_coverages | [ClaimCoverage](/sdk/data-claim/#claimcoverage)[]  
requests | [EligibilityRequest](/sdk/data-eligibility-response/#eligibilityrequest)[]  
eligibility_responses | [EligibilityResponse](/sdk/data-eligibility-response/#eligibilityresponse)[]  
###  Transactor 
Field Name | Type  
---|---  
dbid | Integer  
payer_id | String  
name | String  
type | String  
transactor_type | TransactorType  
clearinghouse_payer | Boolean  
institutional | Boolean  
institutional_enrollment_req | Boolean  
professional | Boolean  
professional_enrollment_req | Boolean  
era | Boolean  
era_enrollment_req | Boolean  
eligibility | Boolean  
eligibility_enrollment_req | Boolean  
workers_comp | Boolean  
secondary_support | Boolean  
claim_fee | Boolean  
remit_fee | Boolean  
state | String  
description | String  
active | Boolean  
use_provider_for_eligibility | Boolean  
supports_eligibility_check | Boolean (computed)  
use_for_submission | Transactor  
used_for_submission_by | Transactor[]  
coverage_types | TransactorCoverageType[]  
vaccines | [Vaccine](/sdk/data-vaccine/#vaccine)[]  
addresses | TransactorAddress[]  
coverages | Coverage[]  
phones | TransactorPhone[]  
specific_charges | [PayorSpecificCharge](/sdk/data-payor-specific-charge/#payorspecificcharge)[]  
remits | [BaseRemittanceAdvice](/sdk/data-posting/#baseremittanceadvice)[]  
###  TransactorAddress 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
line1 | String  
line2 | String  
city | String  
district | String  
state_code | String  
postal_code | String  
use | [AddressUse](/sdk/data-enumeration-types/#addressuse)  
type | [AddressType](/sdk/data-enumeration-types/#addresstype)  
longitude | Float  
latitude | Float  
start | Date  
end | Date  
country | String  
state | [AddressState](/sdk/data-enumeration-types/#addressstate)  
transactor | Transactor  
coverages | [Coverage](/sdk/data-coverage/#coverage)[]  
###  TransactorPhone 
Field Name | Type  
---|---  
id | UUIDField  
dbid | Integer  
created | DateTime  
modified | DateTime  
system | String  
value | String  
use | [ContactPointUse](/sdk/data-enumeration-types/#contactpointuse)  
use_notes | String  
rank | Integer  
state | [ContactPointState](/sdk/data-enumeration-types/#contactpointstate)  
transactor | Transactor  
coverages | [Coverage](/sdk/data-coverage/#coverage)[]  
###  EligibilitySummary 
Field Name | Type  
---|---  
id | UUIDField  
dbid | Integer  
created | DateTime  
modified | DateTime  
coverage | [Coverage](/sdk/data-coverage/#coverage)  
copay_cents | Integer  
coinsurance | Integer  
##  Enumeration types 
###  CoverageStack 
Value | Label  
---|---  
IN_USE | In use  
OTHER | Other  
REMOVED | Removed  
###  CoverageState 
Value | Label  
---|---  
active | Active  
deleted | Deleted  
###  CoverageType 
Value | Label  
---|---  
commercial | Commercial  
workerscomp | Workers Comp  
bcbs | Blue Cross Blue Shield  
champus | Tricare/Champus  
medicaid | Medicaid  
medicare | Medicare  
other | Other  
tpa | Third Party Administrator  
motorvehicle | Motor Vehicle  
lien | Attorney/Lien  
pip | Personal Injury  
###  CoverageRelationshipCode 
Value | Label  
---|---  
18 | Self  
01 | Spouse  
19 | Natural Child, insured has financial responsibility  
43 | Natural Child, insured does not have financial responsibility  
17 | Step Child  
10 | Foster Child  
15 | Ward of the Court  
20 | Employee  
21 | Unknown  
22 | Handicapped Dependent  
39 | Organ donor  
40 | Cadaver donor  
05 | Grandchild  
07 | Niece/Nephew  
41 | Injured Plaintiff  
23 | Sponsored Dependent  
24 | Minor Dependent of a Minor Dependent  
32 | Mother  
33 | Father  
04 | Grandparent  
53 | Life Partner  
29 | Significant Other  
G8 | Other  
###  TransactorCoverageType 
Value | Label  
---|---  
ANNU | annuity policy  
AUTOPOL | automobile  
CHAR | charity program  
COL | collision coverage policy  
CRIME | crime victim program  
DENTAL | dental care policy  
DENTPRG | dental program  
DIS | disability insurance policy  
DISEASE | disease specific policy  
DRUGPOL | drug policy  
EAP | employee assistance program  
EWB | employee welfare benefit plan policy  
ENDRENAL | end renal program  
EHCPOL | extended healthcare  
FLEXP | flexible benefit plan policy  
GOVEMP | government employee health program  
HIP | health insurance plan policy  
HMO | health maintenance organization policy  
HSAPOL | health spending account  
HIRISK | high risk pool program  
HIVAIDS | HIV-AIDS program  
IND | indigenous peoples health program  
LIFE | life insurance policy  
LTC | long term care policy  
MCPOL | managed care policy  
MANDPOL | mandatory health program  
MENTPOL | mental health policy  
MENTPRG | mental health program  
MILITARY | military health program  
pay | Pay  
POS | point of service policy  
PPO | preferred provider organization policy  
PNC | property and casualty insurance policy  
DISEASEPRG | public health program  
PUBLICPOL | public healthcare  
REI | reinsurance policy  
RETIRE | retiree health program  
SAFNET | safety net clinic program  
SOCIAL | social service program  
SUBSIDIZ | subsidized health program  
SUBSIDMC | subsidized managed care program  
SUBSUPP | subsidized supplemental health program  
SUBPOL | substance use policy  
SUBPRG | substance use program  
SURPL | surplus line insurance policy  
TLIFE | term life insurance policy  
UMBRL | umbrella liability insurance policy  
UNINSMOT | uninsured motorist policy  
ULIFE | universal life insurance policy  
VET | veteran health program  
VISPOL | vision care policy  
CANPRG | women's cancer detection program  
WCBPOL | worker's compensation  
###  TransactorType 
Value | Label  
---|---  
commercial | Commercial  
workerscomp | Workers Comp  
champus | Tricare/Champus  
medicaid | Medicaid  
medicare | Medicare  
medicare_advantage | Medicare Advantage  
CHIP | CHIP  
automobile | Automobile  
employer | Employer  
direct_care | Direct Care  
bcbs | Blue Cross Blue Shield
----- END PAGE https://docs.canvasmedical.com/sdk/data-coverage/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-detected-issue/
##  Introduction 
The `DetectedIssue` model represents an actual or potential clinical issue with or between one or more active or proposed clinical actions for a patient.
##  Basic usage 
To get a detected issue by identifier, use the `get` method on the `DetectedIssue` model manager:
    ```python
    from canvas_sdk.v1.data.detected_issue import DetectedIssue
    detected_issue = DetectedIssue.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    ```
If you have a patient object, the detected issues for a patient can be accessed with the `detected_issues` attribute on a `Patient` object:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    detected_issues = patient.detected_issues.all()
    ```
##  Evidence 
The codings for the evidence of a detected issue can be accessed with the `evidence` attribute on a `DetectedIssue` object:
    ```python
    from canvas_sdk.v1.data.detected_issue import DetectedIssue
    from logger import log
    detected_issue = DetectedIssue.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    for coding in detected_issue.evidence.all():
        log.info(f"system:  {coding.system}")
        log.info(f"code:    {coding.code}")
        log.info(f"display: {coding.display}")
    ```
##  Filtering 
Detected issues can be filtered by any attribute that exists on the model.
Filtering for detected issues is done with the `filter` method on the `DetectedIssue` model manager.
###  By attribute 
Specify an attribute with `filter` to filter by that attribute:
    ```python
    from canvas_sdk.v1.data.detected_issue import DetectedIssue
    detected_issues = DetectedIssue.objects.filter(status="active")
    ```
###  Committed detected issues 
The `committed` method returns detected issues that have been committed and not entered in error:
    ```python
    from canvas_sdk.v1.data.detected_issue import DetectedIssue
    committed_detected_issues = DetectedIssue.objects.committed()
    ```
##  Attributes 
###  DetectedIssue 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
identified | DateTime  
originator | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
patient | [Patient](/sdk/data-patient/#patient)  
code | String  
status | String  
severity | String  
reference | String  
issue_identifier | String  
issue_identifier_system | String  
detail | String  
evidence | DetectedIssueEvidence[]  
###  DetectedIssueEvidence 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
system | String  
version | String  
code | String  
display | String  
user_selected | Boolean  
detected_issue | [DetectedIssue](/sdk/data-detected-issue/#detectedissue)  
----- END PAGE https://docs.canvasmedical.com/sdk/data-detected-issue/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-device/
##  Introduction 
The `Device` model represents a type of a manufactured item that is used in the provision of healthcare without being substantially changed through that activity. The device may be a medical or non-medical device.
##  Basic usage 
To get a device by identifier, use the `get` method on the `Device` model manager:
    ```python
    from canvas_sdk.v1.data.device import Device
    device = Device.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    ```
If you have a patient object, the devices for a patient can be accessed with the `devices` attribute on a `Patient` object:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    devices = patient.devices.all()
    ```
##  Filtering 
Devices can be filtered by any attribute that exists on the model.
Filtering for devices is done with the `filter` method on the `Device` model manager.
###  By attribute 
Specify an attribute with `filter` to filter by that attribute:
    ```python
    from canvas_sdk.v1.data.device import Device
    devices = Device.objects.filter(manufacturer="ACME Biomedical", lot_number="M320")
    ```
###  Committed devices 
The `committed` method returns devices that have been committed and not entered in error:
    ```python
    from canvas_sdk.v1.data.device import Device
    committed_devices = Device.objects.committed()
    ```
##  Attributes 
###  Device 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
originator | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
patient | [Patient](/sdk/data-patient/#patient)  
note_id | Integer  
labeled_contains_NRL | Boolean  
assigning_authority | String  
scoping_entity | String  
udi | String  
di | String  
issuing_agency | String  
lot_number | String  
brand_name | String  
mri_safety_status | String  
version_model_number | String  
company_name | String  
gmdnPTName | String  
status | String  
expiration_date | Date  
expiration_date_original | String  
serial_number | String  
manufacturing_date_original | String  
manufacturing_date | Date  
manufacturer | String  
procedure_id | Integer  
----- END PAGE https://docs.canvasmedical.com/sdk/data-device/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-diagnostic-view/
##  Introduction 
The `DiagnosticView` model represents a saved combination of lab tests and questionnaire codes configured on your instance. A diagnostic view has no patient of its own — it is a reusable definition. When a diagnostic view is embedded in a note with the [Reference](/sdk/commands/#reference) command, Canvas renders that patient's results for the view's codes as a timeseries.
Diagnostic views are configured by an administrator, so the set available to a plugin is whatever your instance has defined.
##  Basic usage 
To get a diagnostic view by identifier, use the `get` method on the `DiagnosticView` model manager:
    ```python
    from canvas_sdk.v1.data import DiagnosticView
    view = DiagnosticView.objects.get(id="dca3a3c5-0a8e-4f7b-9c6a-1b9bf3a6e5e0")
    ```
To list every diagnostic view on the instance:
    ```python
    from canvas_sdk.v1.data import DiagnosticView
    views = DiagnosticView.objects.all()
    ```
##  Filtering 
Diagnostic views can be filtered by any attribute that exists on the model.
###  By name 
Names are set by whoever configured the view, so match on the exact name you expect and handle the case where it is absent:
    ```python
    from canvas_sdk.v1.data import DiagnosticView
    a1c_view = DiagnosticView.objects.filter(name="Hemoglobin A1c").first()
    ```
###  By search tag 
`tags` is a single free-text string of search terms, not a list, so use a substring match:
    ```python
    from canvas_sdk.v1.data import DiagnosticView
    diabetes_views = DiagnosticView.objects.filter(tags__icontains="diabetes")
    ```
##  Embedding a view in a note 
Pass the view's `id` to the [Reference](/sdk/commands/#reference) command:
    ```python
    from canvas_sdk.commands import ReferenceCommand
    from canvas_sdk.v1.data import DiagnosticView
    def compute():
        a1c_view = DiagnosticView.objects.filter(name="Hemoglobin A1c").first()
        if not a1c_view:
            return []
        reference = ReferenceCommand(
            note_uuid="8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47",
            diagnostic_view_id=a1c_view.id,
        )
        return [reference.originate(commit=True)]
    ```
##  Attributes 
###  DiagnosticView 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
name | String (up to 100 characters)  
tags | String (up to 500 characters; free-text search terms)  
originator | [CanvasUser](/sdk/data-canvasuser)
----- END PAGE https://docs.canvasmedical.com/sdk/data-diagnostic-view/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-document-reference/
#  DocumentReference 
The `DocumentReference` model represents references to documents stored in Canvas, such as uploaded PDFs, scanned files, and other clinical documents. Each document reference can link to a file stored in S3 and provides secure access via presigned URLs.
##  Basic Usage 
    ```python
    from canvas_sdk.v1.data import DocumentReference
    # Get a specific document reference
    doc_ref = DocumentReference.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    # Get all document references
    all_docs = DocumentReference.objects.all()
    ```
##  Filtering 
###  By patient 
    ```python
    from canvas_sdk.v1.data import DocumentReference
    patient_docs = DocumentReference.objects.for_patient("b80b1cdc2e6a4aca90ccebc02e683f35")
    ```
###  By status 
    ```python
    from canvas_sdk.v1.data import DocumentReference, DocumentReferenceStatus
    current_docs = DocumentReference.objects.filter(status=DocumentReferenceStatus.CURRENT)
    ```
###  By category or type 
    ```python
    from canvas_sdk.v1.data import DocumentReference
    docs = DocumentReference.objects.filter(category__code="clinical-note")
    ```
##  Accessing Document Files 
The `document_url` property returns a presigned S3 URL for securely accessing the document file. If no S3 file is present, it falls back to the `document_absolute_url` field.
    ```python
    from canvas_sdk.v1.data import DocumentReference
    doc_ref = DocumentReference.objects.exclude(document="").first()
    # Returns a presigned S3 URL (valid for 1 hour)
    url = doc_ref.document_url
    ```
##  Attributes 
###  DocumentReference 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
document | String  
document_absolute_url | String  
document_content_type | String  
business_identifier | String  
originator | [CanvasUser](/sdk/data-canvasuser)  
subject | [CanvasUser](/sdk/data-canvasuser)  
type | DocumentReferenceCoding  
category | DocumentReferenceCategory  
status | DocumentReferenceStatus  
date | Date  
encounter | [Encounter](/sdk/data-encounter)  
team | [Team](/sdk/data-team/#team)  
related_object_document_title | String  
related_object_document_comment | String  
content_type | [ContentType](/sdk/data-content-type/) (the related object's type)  
object_id | Integer (the related object's `dbid`)  
related_object | Model (property) — the SDK object the document is attached to, or `None`  
document_url | String (property) — presigned S3 URL or absolute URL  
###  DocumentReferenceCoding 
A coding entry representing the type of a document reference.
Field Name | Type  
---|---  
dbid | Integer  
system | String  
version | String  
code | String  
display | String  
user_selected | Boolean  
###  DocumentReferenceCategory 
A coding entry representing the category of a document reference.
Field Name | Type  
---|---  
dbid | Integer  
system | String  
version | String  
code | String  
display | String  
user_selected | Boolean  
###  DocumentReferenceStatus 
An enum representing the status of a document reference.
Member | Value | Description  
---|---|---  
`CURRENT` | `current` | Current  
`SUPERSEDED` | `superseded` | Superseded  
`ENTERED_IN_ERROR` | `entered-in-error` | Entered in Error  
##  The related object 
Most document references point back at the record they were generated from — a lab report, a letter, a locked-note PDF, a patient statement, and so on. `content_type` and `object_id` form that generic link: `content_type` identifies the linked model by its stable `app_label` and lowercased `model` name, and `object_id` is that record's `dbid`.
The `related_object` property resolves the link for you, returning the corresponding SDK data model instance:
    ```python
    from canvas_sdk.v1.data import DocumentReference
    doc_ref = DocumentReference.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    # The SDK object this document is attached to (a LabReport, Letter, ImagingReport, ...), or None.
    source = doc_ref.related_object
    ```
`related_object` returns `None` when the document has no related object (`content_type` or `object_id` is unset) or when the linked content type has no SDK data model equivalent. The content types it resolves today:
`app_label` / `model` | SDK data model  
---|---  
`api` / `labreport` | [LabReport](/sdk/data-labs/#labreport)  
`api` / `imagingreport` | [ImagingReport](/sdk/data-imaging/#imagingreport)  
`api` / `letter` | [Letter](/sdk/data-letter/#letter)  
`api` / `notestatechangeevent` | [NoteStateChangeEvent](/sdk/data-note/#notestatechangeevent)  
`api` / `uncategorizedclinicaldocument` | [UncategorizedClinicalDocument](/sdk/data-uncategorized-clinical-document/#uncategorizedclinicaldocument)  
`api` / `referralreport` | [ReferralReport](/sdk/data-referral/#referralreport)  
`api` / `educationalmaterial` | [EducationalMaterial](/sdk/data-educational-material/#educationalmaterial)  
`api` / `patientadministrativedocument` | [PatientAdministrativeDocument](/sdk/data-patient-administrative-document/#patientadministrativedocument)  
`quality_and_revenue` / `invoicefull` | [Invoice](/sdk/data-invoice/#invoice)  
To go the other way — find every document reference for a given source type — resolve the [ContentType](/sdk/data-content-type/) at runtime from its stable `app_label` and `model` (never hardcode the per-environment `dbid`) and filter on it:
    ```python
    from canvas_sdk.v1.data import ContentType, DocumentReference
    content_type = ContentType.objects.filter(
        app_label="api", model="patientadministrativedocument"
    ).first()
    references = DocumentReference.objects.filter(content_type=content_type)
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/data-document-reference/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-document-review-delegation/
##  Introduction 
The `DocumentReviewDelegation` model records a hand-off of a document review from one staff member to another staff member or team. When a reviewer delegates an uncategorized clinical document, Canvas stores who delegated it, who received it, the original owner, whether the recipient may apply the owner's signature, and any instructions.
Delegations are an append-only log: a document has at most one **active** delegation at a time (`is_active`). Delegation is A↔B only — an owner delegates a document out, and the recipient may only route it back — so `on_behalf_of` always identifies the original owner and, when `signature_consent` is set, the staff member whose signature the recipient may apply while annotating the document.
##  Basic usage 
To get a delegation by identifier, use the `get` method on the `DocumentReviewDelegation` model manager:
    ```python
    from canvas_sdk.v1.data import DocumentReviewDelegation
    delegation = DocumentReviewDelegation.objects.get(id="b5a0c1d2-e3f4-5678-9abc-def012345678")
    ```
If you have an [UncategorizedClinicalDocument](/sdk/data-uncategorized-clinical-document/), its delegations are available through the `delegations` and `active_delegation` accessors:
    ```python
    from canvas_sdk.v1.data import UncategorizedClinicalDocument
    document = UncategorizedClinicalDocument.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    # The full delegation history, oldest first.
    history = document.delegations
    # The current active delegation, or None when the document is with its owner.
    current = document.active_delegation
    if current and current.signature_consent:
        signer = current.on_behalf_of  # whose signature the recipient may apply
    ```
##  Filtering 
Delegations can be filtered by any attribute that exists on the model.
###  Active delegations 
    ```python
    from canvas_sdk.v1.data import DocumentReviewDelegation
    active = DocumentReviewDelegation.objects.filter(is_active=True)
    ```
###  Delegations that granted signature consent 
    ```python
    from canvas_sdk.v1.data import DocumentReviewDelegation
    with_consent = DocumentReviewDelegation.objects.filter(is_active=True, signature_consent=True)
    ```
##  Route-back 
Use the `is_route_back` property to tell whether an active delegation returned the document to its owner (as opposed to delegating it away):
    ```python
    from canvas_sdk.v1.data import UncategorizedClinicalDocument
    document = UncategorizedClinicalDocument.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    delegation = document.active_delegation
    if delegation and delegation.is_route_back:
        ...  # the document is back with its owner
    ```
##  Attributes 
###  DocumentReviewDelegation 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
content_type | [ContentType](/sdk/data-content-type/) (the delegated document's type)  
object_id | Integer (the delegated document's `dbid`)  
delegated_by | [Staff](/sdk/data-staff/#staff) (who handed the document off)  
delegated_to_staff | [Staff](/sdk/data-staff/#staff) (recipient, if delegated to a person)  
delegated_to_team | [Team](/sdk/data-team/#team) (recipient, if delegated to a team)  
on_behalf_of | [Staff](/sdk/data-staff/#staff) (the original owner)  
signature_consent | Boolean (may the recipient apply the owner's signature)  
comment | String (instructions for the recipient)  
is_active | Boolean (the current delegation for the document)  
##  The delegated document 
`content_type` and `object_id` form a generic link to the document whose review was delegated: `content_type` identifies the linked model, and `object_id` is that record's `dbid`. Review delegation is currently supported only for [UncategorizedClinicalDocument](/sdk/data-uncategorized-clinical-document/) records, so `content_type` always resolves to that model and `object_id` is the document's `dbid`. The generic relation leaves room for additional document types in the future.
The most direct way to work with a document's delegations is from the document itself, through its `delegations` and `active_delegation` accessors:
    ```python
    from canvas_sdk.v1.data import UncategorizedClinicalDocument
    document = UncategorizedClinicalDocument.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    current = document.active_delegation   # the active DocumentReviewDelegation, or None
    history = document.delegations         # every delegation hop recorded for the document
    ```
To go the other way — from a delegation to the document it points at — read `content_type` to learn which model `object_id` refers to, then resolve it. A [ContentType](/sdk/data-content-type/) is identified by its stable `app_label` and `model` (the lowercased model name), so branch on those rather than on the per-environment `dbid`. This keeps working if more document types become delegatable later:
    ```python
    from canvas_sdk.v1.data import DocumentReviewDelegation, UncategorizedClinicalDocument
    delegation = DocumentReviewDelegation.objects.get(id="b3e6f74c-2a1b-4c8d-9f2e-31842ae7d3b9")
    content_type = delegation.content_type
    # Today content_type is always api / uncategorizedclinicaldocument; object_id is its dbid.
    if content_type.app_label == "api" and content_type.model == "uncategorizedclinicaldocument":
        document = UncategorizedClinicalDocument.objects.get(dbid=delegation.object_id)
    ```
You can also use `content_type` to find every delegation for a given document type. Resolve the [ContentType](/sdk/data-content-type/) at runtime from its stable `app_label` and `model` — never hardcode the `dbid`, which differs per environment:
    ```python
    from canvas_sdk.v1.data import ContentType, DocumentReviewDelegation
    document_ct = ContentType.objects.filter(app_label="api", model="uncategorizedclinicaldocument").first()
    delegations = DocumentReviewDelegation.objects.filter(content_type=document_ct)
    ```
Exactly one of `delegated_to_staff` / `delegated_to_team` is set on a delegation.
----- END PAGE https://docs.canvasmedical.com/sdk/data-document-review-delegation/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-educational-material/
##  Introduction 
The `EducationalMaterial` model represents patient educational material recorded on a note through the Educational Material command — the selected article, its title and abstract, and the languages it is available in.
Records are returned regardless of command state, so staged commands are included; use `committed()` to limit results to committed commands.
##  Basic Usage 
`EducationalMaterial` records can be retrieved by their UUID `id`, their integer `dbid`, or through a patient.
    ```python
    from canvas_sdk.v1.data import EducationalMaterial
    # Get all educational material records
    materials = EducationalMaterial.objects.all()
    # Get a specific record by its UUID id
    material = EducationalMaterial.objects.get(id="c9a7b1e2-d4f3-4e6a-8b5c-0d1e2f3a4b5c")
    ```
If you have a `Patient` object, its educational material records can be accessed with the `education_material` reverse relation:
    ```python
    from canvas_sdk.v1.data import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    materials = patient.education_material.all()
    ```
##  Filtering 
###  By attribute 
    ```python
    from canvas_sdk.v1.data import EducationalMaterial
    materials = EducationalMaterial.objects.filter(selected_language="en-us")
    ```
###  Committed records 
The `committed` method returns records that have been committed and not entered in error:
    ```python
    from canvas_sdk.v1.data import EducationalMaterial
    committed = EducationalMaterial.objects.committed()
    ```
##  Accessing the article PDF 
`EducationalMaterial` holds the article's metadata — its title, abstract, and languages — not the article file itself. When the command is committed, Canvas renders the article to a PDF and attaches it to a [DocumentReference](/sdk/data-document-reference/) with the LOINC type `34895-3` (Education note).
To read a patient's education note PDFs, filter `DocumentReference` by that type and use its `document_url`:
    ```python
    from canvas_sdk.v1.data import DocumentReference
    education_notes = DocumentReference.objects.for_patient(
        "1eed3ea2a8d546a1b681a2a45de1d790"
    ).filter(type__code="34895-3")
    for note in education_notes:
        url = note.document_url
    ```
To resolve the PDF for one specific record, filter on the document's [related object](/sdk/data-document-reference/#the-related-object) instead. Resolve the [ContentType](/sdk/data-content-type/) at runtime from its stable `app_label` and `model` — never hardcode the per-environment `dbid` — and match `object_id` against the material's `dbid`:
    ```python
    from canvas_sdk.v1.data import ContentType, DocumentReference, EducationalMaterial
    material = EducationalMaterial.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    content_type = ContentType.objects.filter(
        app_label="api", model="educationalmaterial"
    ).first()
    document = DocumentReference.objects.filter(
        content_type=content_type, object_id=material.dbid
    ).first()
    url = document.document_url if document else None
    ```
> **Info:** `object_id` holds the related record's integer `dbid`, not its UUID `id`, so filter on `material.dbid`. 
##  Attributes 
###  EducationalMaterial 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
originator | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
patient | [Patient](/sdk/data-patient/#patient)  
note | [Note](/sdk/data-note/#note)  
article_id | String  
selected_language | EducationalMaterialLanguage  
title | String  
languages | String[]  
abstract | String  
`selected_language` defaults to `en-us`.
`languages` holds the locale codes the article is available in, drawn from the same set of codes as EducationalMaterialLanguage. It is stored as a plain array of strings rather than an enum, so compare against the code values (`"es-us"`) rather than expecting enum members.
##  Enumeration types 
###  EducationalMaterialLanguage 
Value | Label  
---|---  
en-us | English  
es-us | Spanish  
en-ca | English CA  
fr-ca | French CA  
fr-fr | French FR  
da-dk | Danish DK  
ar-eg | Arabic Egypt  
ar-us | Arabic  
bn-us | Bengali  
bs-ba | Bosnian  
bs-us | Bosnian  
fa-ir | Farsi Iran  
fa-us | Farsi  
hr-hr | Croatian  
ht-us | Haitian  
ko-us | Korean  
ru-ru | Russian  
ru-us | Russian  
sr-us | Serbian  
so-so | Somalia  
so-us | Somalia  
tl-us | Tagalog  
vi-vn | Vietnamese  
vi-us | Vietnamese  
zh-cn | Chinese  
zh-us | Chinese
----- END PAGE https://docs.canvasmedical.com/sdk/data-educational-material/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-eligibility-response/
##  Introduction 
The `EligibilityResponse` model represents a coverage eligibility (271) response returned by a payer for a patient's `Coverage`, along with the originating `EligibilityRequest` (270). An `EligibilityResponse` also derives a check `status` (Active, Inactive, or Failed) from the payer's response.
##  Basic usage 
To get an eligibility response by identifier, use the `get` method on the `EligibilityResponse` model manager:
    ```python
    from canvas_sdk.v1.data.eligibility_response import EligibilityResponse
    response = EligibilityResponse.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    ```
Eligibility requests and responses are linked to a `Coverage`. From a coverage object, use the `requests` and `eligibility_responses` attributes:
    ```python
    from canvas_sdk.v1.data.coverage import Coverage
    coverage = Coverage.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    requests = coverage.requests.all()
    responses = coverage.eligibility_responses.all()
    ```
##  Eligibility status 
`EligibilityResponse.status` returns an `EligibilityResponseStatus` derived from the payer's response — `FAILED` when the check errored, `INACTIVE` when the payer reports an inactive benefit section, otherwise `ACTIVE`:
    ```python
    from canvas_sdk.v1.data.coverage import Coverage
    from canvas_sdk.v1.data.eligibility_response import EligibilityResponseStatus
    coverage = Coverage.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    response = coverage.eligibility_responses.order_by("created").last()
    is_active = response is not None and response.status == EligibilityResponseStatus.ACTIVE
    ```
A coverage with no eligibility responses (an empty `coverage.eligibility_responses` queryset) has not been verified.
`NOT_APPLICABLE`, like `UNKNOWN`, is a value returned by [`Coverage.eligibility_status`](/sdk/data-coverage/#eligibility-status), never by an individual `EligibilityResponse.status`. A single response only ever resolves to `FAILED`, `INACTIVE`, or `ACTIVE`.
##  Attributes 
###  EligibilityRequest 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
coverage | [Coverage](/sdk/data-coverage)  
trading_partner_id | String  
member | JSON  
provider | JSON  
payload | String  
control_number | String  
###  EligibilityResponse 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
eligibility_request | EligibilityRequest  
coverage | [Coverage](/sdk/data-coverage)  
client_id | String  
correlation_id | String  
deductible | JSON  
out_of_pocket | JSON  
coverage_info | JSON  
payer | JSON  
provider | JSON  
service_type_codes | List[String]  
service_types | List[String]  
subscriber | JSON  
trading_partner_id | String  
valid_request | Boolean  
errors | List[String]  
eligid | String  
x12_response | String  
parsed_x12_response | JSON  
status | EligibilityResponseStatus (computed)  
eligibility_or_benefit_information | List (computed)  
`status` and `eligibility_or_benefit_information` are computed from `errors` and `parsed_x12_response` rather than stored, so neither can be used in `filter()`. To select responses by outcome, filter on the columns they derive from — a failed check is one with a non-empty `errors`:
    ```python
    from canvas_sdk.v1.data.eligibility_response import EligibilityResponse
    failed = EligibilityResponse.objects.exclude(errors=None).exclude(errors=[])
    ```
##  Enumeration types 
###  EligibilityResponseStatus 
Name | Value  
---|---  
ACTIVE | Active  
INACTIVE | Inactive  
FAILED | Failed  
UNKNOWN | Unknown  
NOT_APPLICABLE | NotApplicable  
----- END PAGE https://docs.canvasmedical.com/sdk/data-eligibility-response/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-encounter/
##  Introduction 
The `Encounter` model represents a patient encounter connected to a Note in Canvas.
##  Basic usage 
To get an encounter by identifier, use the `get` method on the `Encounter` model manager:
    ```python
    from canvas_sdk.v1.data import Encounter
    encounter = Encounter.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    ```
To get an encounter from a note, use the `encounter` attribute on the `Note` object:
    ```python
    from canvas_sdk.v1.data import Note
    note = Note.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    encounter = note.encounter
    ```
Keep in mind that not all notes have an associated encounter, so sometimes `note.encounter` will be `None`.
Similary, you can get a note from an `Encounter` object by using the `note` attribute:
    ```python
    from canvas_sdk.v1.data import Encounter
    encounter = Encounter.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    note = encounter.note
    ```
All encounters will have an associated note, which means `encounter.note` will never be `None`.
##  Filtering 
Encounters can be filtered by any attribute that exists on the model.
Filtering for encounters is done with the `filter` method on the `Encounter` model manager.
###  By attribute 
Specify an attribute with `filter` to filter by that attribute:
    ```python
    from canvas_sdk.v1.data.encounter import Encounter, EncounterState
    encounters = Encounter.objects.filter(state=EncounterState.CONCLUDED)
    ```
##  Attributes 
###  Encounter 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
note | [Note](/sdk/data-note/)  
state | EncounterState  
medium | EncounterMedium  
start_time | DateTime  
end_time | DateTime  
document_references | QuerySet[[DocumentReference](/sdk/data-document-reference/#documentreference)]  
##  Enumeration types 
###  EncounterState 
Name | Value  
---|---  
STARTED | STA  
PLANNED | PLA  
CONCLUDED | CON  
CANCELLED | CAN  
###  EncounterMedium 
Name | Value  
---|---  
VOICE | voice  
VIDEO | video  
OFFICE | office  
HOME | home  
OFFSITE | offsite  
LAB | lab  
----- END PAGE https://docs.canvasmedical.com/sdk/data-encounter/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-enumeration-types/
##  Introduction 
This page shows common enumeration types that are used in multiple models.
##  Enumeration types 
###  AddressState 
Value | Label  
---|---  
active | Active  
deleted | Deleted  
###  AddressType 
Value | Label  
---|---  
postal | Postal  
physical | Physical  
both | Both  
###  AddressUse 
Value | Label  
---|---  
home | Home  
work | Work  
temp | Temp  
old | Old  
###  AddressUseWithBilling 
Value | Label  
---|---  
home | Home  
work | Work  
temp | Temp  
old | Old  
billing | Billing  
###  ColorEnum 
Value | Label  
---|---  
red | Red  
orange | Orange  
yellow | Yellow  
olive | Olive  
green | Green  
teal | Teal  
blue | Blue  
violet | Violet  
purple | Purple  
pink | Pink  
brown | Brown  
grey | Grey  
black | Black  
###  ContactPointState 
Value | Label  
---|---  
active | Active  
deleted | Deleted  
###  ContactPointSystem 
Value | Label  
---|---  
phone | phone  
fax | fax  
email | email  
pager | pager  
other | other  
###  ContactPointUse 
Value | Label  
---|---  
home | Home  
work | Work  
temp | Temp  
old | Old  
other | Other  
mobile | Mobile  
automation | Automation  
###  DocumentReviewMode 
Value | Label  
---|---  
RR | Review required  
AR | Already reviewed offline  
RN | Review not required  
###  OrderStatus 
Value | Description  
---|---  
proposed | Proposed  
draft | Draft  
planned | Planned  
requested | Requested  
received | Received  
accepted | Accepted  
in-progress | In-progress  
review | Review  
completed | Completed  
cancelled | Cancelled  
suspended | Suspended  
rejected | Rejected  
failed | Failed  
EIE | Entered in Error  
###  Origin 
Value | Label  
---|---  
REF_CMD | Referral command  
CMP_IMG_ORD | Completing image orders  
IMG_REP_REV | Imaging report review  
LAB_RES_REV | Lab results review  
CON_REP_REV | Consult report review  
UNC_DOC_REP_REV | Uncategorized document report review  
ASN_NOT_PHN_REV | Assigned note/phone call for review  
POP_HLT_OUT | Population health outreach  
CMP_LAB_ORD | Completing lab orders  
CHT_PDF | Chart PDF  
EXP_CLM_SNO | Expired claim snoozed  
FLG_PST_REV | Flagged posting review  
BAT_PTN_STA | Batch patient statements  
INC_COV | Incomplete Coverage  
###  PersonSex 
Value | Label  
---|---  
F | female  
M | male  
O | other  
UNK | unknown  
###  ReviewPatientCommunicationMethod 
Value | Description  
---|---  
DM | delegate call, can leave message  
DA | delegate call, need patient to answer  
DL | delegate letter  
DC | do not communicate  
AM | already left message  
AR | already reviewed with patient  
###  ReviewStatus 
Value | Label  
---|---  
reviewing | reviewing  
reviewed | reviewed  
###  TaxIDType 
Value | Label  
---|---  
E | EIN text  
S | SSN
----- END PAGE https://docs.canvasmedical.com/sdk/data-enumeration-types/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-external-event/
##  Introduction 
The `ExternalEvent` and `ExternalVisit` models represent clinical events from external data sources such as ADT (Admission, Discharge, Transfer) feeds. These models enable tracking of patient encounters that occur outside of Canvas, such as hospital admissions, emergency room visits, or transfers between facilities.
An `ExternalVisit` groups related events for a single patient visit, while `ExternalEvent` represents individual events within that visit (e.g., admission, discharge, transfer).
##  Basic usage 
To get an external event by identifier, use the `get` method on the `ExternalEvent` model manager:
    ```python
    from canvas_sdk.v1.data.external_event import ExternalEvent
    event = ExternalEvent.objects.get(id="b4f8c3a1-2d5e-4f6a-8b9c-1a2b3c4d5e6f")
    ```
To get an external visit:
    ```python
    from canvas_sdk.v1.data.external_event import ExternalVisit
    visit = ExternalVisit.objects.get(id="a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d")
    ```
###  Accessing related models 
If you have an external event, you can access the associated visit and patient:
    ```python
    from canvas_sdk.v1.data.external_event import ExternalEvent
    event = ExternalEvent.objects.get(id="b4f8c3a1-2d5e-4f6a-8b9c-1a2b3c4d5e6f")
    # Access the parent visit
    visit = event.external_visit
    # Access the patient
    patient = event.patient
    ```
If you have an external visit, you can access all events within that visit:
    ```python
    from canvas_sdk.v1.data.external_event import ExternalVisit
    visit = ExternalVisit.objects.get(id="a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d")
    # Get all events in this visit
    events = visit.visit_events.all()
    ```
If you have a patient object, you can access their external events and visits:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    # Get all external events for this patient
    events = patient.patient_events.all()
    # Get all external visits for this patient
    visits = patient.patient_visits.all()
    ```
##  Filtering 
External events and visits can be filtered by any attribute that exists on the model.
###  By patient 
    ```python
    from canvas_sdk.v1.data.external_event import ExternalEvent, ExternalVisit
    # Get all events for a specific patient
    events = ExternalEvent.objects.filter(patient__id="1eed3ea2a8d546a1b681a2a45de1d790")
    # Get all visits for a specific patient
    visits = ExternalVisit.objects.filter(patient__id="1eed3ea2a8d546a1b681a2a45de1d790")
    ```
###  By event type 
    ```python
    from canvas_sdk.v1.data.external_event import ExternalEvent
    # Get all admission events
    admissions = ExternalEvent.objects.filter(event_type="ADT^A01")
    # Get all discharge events
    discharges = ExternalEvent.objects.filter(event_type="ADT^A03")
    ```
###  By cancelled status 
    ```python
    from canvas_sdk.v1.data.external_event import ExternalEvent
    # Get all non-cancelled events
    active_events = ExternalEvent.objects.filter(event_cancelation_datetime__isnull=True)
    # Get all cancelled events
    cancelled_events = ExternalEvent.objects.filter(event_cancelation_datetime__isnull=False)
    ```
###  By visit identifier 
    ```python
    from canvas_sdk.v1.data.external_event import ExternalVisit
    visit = ExternalVisit.objects.get(visit_identifier="VISIT-12345")
    ```
###  By facility 
    ```python
    from canvas_sdk.v1.data.external_event import ExternalVisit
    visits = ExternalVisit.objects.filter(facility_name="General Hospital")
    ```
##  Attributes 
###  ExternalEvent 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
external_visit | ExternalVisit  
patient | [Patient](/sdk/data-patient/#patient)  
message_control_id | String  
message_datetime | DateTime  
event_type | String  
event_datetime | DateTime  
event_cancelation_datetime | DateTime  
raw_message | String  
cancelled | Boolean (property)  
###  ExternalVisit 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
patient | [Patient](/sdk/data-patient/#patient)  
visit_identifier | String  
information_source | String  
facility_name | String  
visit_events | QuerySet[ExternalEvent]  
##  Common Event Types 
External events typically use HL7 ADT event types:
Event Type | Description  
---|---  
ADT^A01 | Admit/Visit Notification  
ADT^A02 | Transfer a Patient  
ADT^A03 | Discharge/End Visit  
ADT^A04 | Register a Patient  
ADT^A08 | Update Patient Information  
ADT^A11 | Cancel Admit/Visit Notification  
ADT^A12 | Cancel Transfer  
ADT^A13 | Cancel Discharge/End Visit  
    ```python
    from canvas_sdk.v1.data.external_event import ExternalEvent
    from logger import log
    # Get recent events for a patient and log their types
    patient_id = "1eed3ea2a8d546a1b681a2a45de1d790"
    events = ExternalEvent.objects.filter(patient__id=patient_id).order_by("-event_datetime")[:10]
    for event in events:
        status = "CANCELLED" if event.cancelled else "ACTIVE"
        log.info(f"Event: {event.event_type} at {event.event_datetime} [{status}]")
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/data-external-event/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-facility/
##  Introduction 
The `Facility` object represents a healthcare facility associated with patients within Canvas. Facilities can include hospitals, clinics, or other healthcare institutions where patients receive care. This object contains essential information about the facility, such as its address, contact details, and operational status.
##  Basic Usage 
To get a facility by identifier, use the `get` method on the `Facility` model manager:
    ```python
    from canvas_sdk.v1.data.facility import Facility
    facility = Facility.objects.get(id="34b50dfa-1b3e-4dc2-a11d-41b3115c29f3")
    ```
##  Filtering 
Facilities can be filtered by any attribute that exists on the model.
Filtering for facilities is done with the `filter` method on the `Facility` model manager.
##  Attributes 
Specify attributes with `filter` to filter by those attributes:
    ```python
    from canvas_sdk.v1.data.facility import Facility
    facilities = Facility.objects.filter(name="General Hospital", city="Metropolis")
    ```
###  Facility 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
line1 | String  
line2 | String  
city | String  
district | String  
state_code | String  
postal_code | String  
name | String  
npi_number | String  
phone_number | String  
fax_number | String  
active | Boolean  
patient_facilities | QuerySet[[PatientFacilityAddress](/sdk/data-patient/#patientfacilityaddress)]
----- END PAGE https://docs.canvasmedical.com/sdk/data-facility/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-family-history/
##  Introduction 
The `FamilyHistory` model represents a patient's family medical history — the condition(s) recorded for one of the patient's relatives, captured by the `family_history` command. The relative is identified by a SNOMED code and term, and the condition(s) are stored as `FamilyHistoryCoding` records reachable through the `coding` accessor.
##  Basic usage 
To get a family history record by identifier, use the `get` method on the `FamilyHistory` model manager:
    ```python
    from canvas_sdk.v1.data.family_history import FamilyHistory
    family_history = FamilyHistory.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    ```
If you have a patient object, a patient's family history can be accessed with the `family_histories` attribute on a `Patient` object:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    family_histories = patient.family_histories.all()
    ```
If you have a patient ID, you can get the family history for the patient with the `for_patient` method on the `FamilyHistory` model manager:
    ```python
    from canvas_sdk.v1.data.family_history import FamilyHistory
    patient_id = "1eed3ea2a8d546a1b681a2a45de1d790"
    family_histories = FamilyHistory.objects.for_patient(patient_id)
    ```
##  Codings 
The relative's condition coding records can be accessed with the `coding` attribute on a `FamilyHistory` object. `FamilyHistory` exposes this relation as the singular `coding`, unlike the plural `codings` on Condition, Procedure, and Immunization:
    ```python
    from canvas_sdk.v1.data.family_history import FamilyHistory
    from logger import log
    family_history = FamilyHistory.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    for coding in family_history.coding.all():
        log.info(f"system:  {coding.system}")
        log.info(f"code:    {coding.code}")
        log.info(f"display: {coding.display}")
    ```
##  Filtering 
Family history records can be filtered by any attribute that exists on the model.
###  By attribute 
Specify an attribute with `filter` to filter by that attribute:
    ```python
    from canvas_sdk.v1.data.family_history import FamilyHistory
    family_histories = FamilyHistory.objects.filter(relation_snomed_term="Mother")
    ```
###  By ValueSet 
See [Value Sets](/sdk/data-value-sets/) for the library of built-in value sets and how to create your own.
Filtering by ValueSet works a little differently. The `find` method on the model manager is used to perform `ValueSet` filtering, matching against the relative's condition coding records — the `coding` accessor — not the `relation_snomed_code`/`relation_snomed_term` fields:
    ```python
    from canvas_sdk.v1.data.family_history import FamilyHistory
    from canvas_sdk.value_set.v2022.condition import Diabetes
    family_histories = FamilyHistory.objects.find(Diabetes)
    ```
###  By coding 
To filter on coding records directly instead of a value set, filter across the relation to match the relative's condition coding records:
    ```python
    from canvas_sdk.v1.data.family_history import FamilyHistory
    family_histories = FamilyHistory.objects.filter(
        coding__code__in=["44054006", "46635009"],
    ).distinct()
    ```
##  Attributes 
###  FamilyHistory 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
deleted | Boolean  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
patient | [Patient](/sdk/data-patient/#patient)  
note | [Note](/sdk/data-note)  
relation_snomed_code | Integer  
relation_snomed_term | String  
narrative | String  
coding | FamilyHistoryCoding[]  
###  FamilyHistoryCoding 
Field Name | Type  
---|---  
dbid | Integer  
system | String  
version | String  
code | String  
display | String  
user_selected | Boolean  
family_history | FamilyHistory  
----- END PAGE https://docs.canvasmedical.com/sdk/data-family-history/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-follow-up/
##  Introduction 
The `FollowUp` model is the anchor for the [Follow Up](/sdk/commands/#followup) command — a requested follow-up (recall) recorded on a Note for a Patient.
##  Basic usage 
To get a follow up by identifier, use the `get` method on the `FollowUp` model manager:
    ```python
    from canvas_sdk.v1.data.follow_up import FollowUp
    follow_up = FollowUp.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    ```
If you have a patient or note object, the follow ups can be accessed with the `follow_ups` attribute:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    from canvas_sdk.v1.data.note import Note
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    follow_ups = patient.follow_ups.all()
    note = Note.objects.get(id="89992c23-c298-4118-864a-26cb3e1ae822")
    follow_ups = note.follow_ups.all()
    ```
##  Committed follow ups 
The `committed` method returns follow ups that have been committed and not entered in error:
    ```python
    from canvas_sdk.v1.data.follow_up import FollowUp
    committed_follow_ups = FollowUp.objects.committed()
    ```
##  Attributes 
###  FollowUp 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
originator | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
patient | [Patient](/sdk/data-patient/#patient)  
note | [Note](/sdk/data-note)  
appointment_note | [Note](/sdk/data-note)  
requested_appointment_date | Date  
requested_appointment_date_original_input | String  
reason_for_visit | String  
reason_for_visit_coding | String  
note_to_patient | String  
internal_comment | String  
requested_appointment_type | EncounterMedium  
requested_note_type | [NoteType](/sdk/data-note)  
##  Enumeration types 
###  EncounterMedium 
Name | Value  
---|---  
VOICE | voice  
VIDEO | video  
OFFICE | office  
HOME | home  
OFFSITE | offsite  
LAB | lab  
----- END PAGE https://docs.canvasmedical.com/sdk/data-follow-up/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-goal/
##  Introduction 
The `Goal` model represents a patient Goal in Canvas, which is always associated with a Note and a Patient.
This page also documents UpdateGoal, the record of a goal's updates and closures, created by committing an [UpdateGoal command](/sdk/commands/#updategoal) or [CloseGoal command](/sdk/commands/#closegoal).
##  Basic usage 
To get a goal by identifier, use the `get` method on the `Goal` model manager:
    ```python
    from canvas_sdk.v1.data.goal import Goal
    goal = Goal.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    ```
If you have a patient object, or note object, the goals for a patient or note can be accessed with the `goals` attribute on a `Patient` or `Note` object:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    from canvas_sdk.v1.data.note import Note
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    goals = patient.goals.all()
    note = Note.objects.get(id="89992c23-c298-4118-864a-26cb3e1ae822")
    goals = note.goals.all()
    ```
`UpdateGoal` records can be queried the same way, and each one links back to the goal it updates with the `goal` attribute:
    ```python
    from canvas_sdk.v1.data.goal import UpdateGoal
    update = UpdateGoal.objects.get(id="61a1853f-168f-4ed3-80d2-44e5d144bcf3")
    goal = update.goal
    committed_updates = UpdateGoal.objects.committed()
    ```
##  Filtering 
Goals can be filtered by any attribute that exists on the model.
Filtering for goals is done with the `filter` method on the `Goal` model manager.
###  By attribute 
Specify an attribute with `filter` to filter by that attribute:
    ```python
    from canvas_sdk.v1.data.goal import Goal, GoalAchievementStatus
    goals = Goal.objects.filter(achievement_status=GoalAchievementStatus.IN_PROGRESS)
    ```
###  Committed goals 
The `committed` method returns goals that have been committed and not entered in error:
    ```python
    from canvas_sdk.v1.data.goal import Goal
    committed_goals = Goal.objects.committed()
    ```
##  Goal updates and closures 
Each change to a goal — via the [UpdateGoal](/sdk/commands/#updategoal) or [CloseGoal](/sdk/commands/#closegoal) command — is recorded as an `UpdateGoal`. Update actions revise the goal while leaving it active; close actions also move it to a closed `lifecycle_status` (e.g. `completed`, `cancelled`, `rejected`). A goal's updates are reachable through its `updates` accessor:
    ```python
    from canvas_sdk.v1.data.goal import Goal
    goal = Goal.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    # Every update or close recorded against this goal.
    updates = goal.updates.all()
    # The most recent committed update — the goal's current state — or None.
    latest = goal.updates.committed().order_by("dbid").last()
    ```
`UpdateGoal` carries the same status, priority, and progress fields as `Goal` (without `goal_statement` / `start_date`), plus a `goal` foreign key back to the goal it updates. Like `Goal`, its manager supports `committed()` to filter to committed, non-entered-in-error records.
##  Attributes 
###  Goal 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
originator | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
patient | [Patient](/sdk/data-patient/#patient)  
note | [Note](/sdk/data-note)  
lifecycle_status | GoalLifecycleStatus  
achievement_status | GoalAchievementStatus  
priority | GoalPriority  
due_date | Date  
start_date | Date  
progress | String  
goal_statement | String  
updates | QuerySet[UpdateGoal]  
###  UpdateGoal 
An update or close action recorded against a Goal, reachable from a goal via `goal.updates`. Written by the [UpdateGoal](/sdk/commands/#updategoal) and [CloseGoal](/sdk/commands/#closegoal) commands.
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
originator | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
patient | [Patient](/sdk/data-patient/#patient)  
note | [Note](/sdk/data-note)  
goal | Goal  
lifecycle_status | GoalLifecycleStatus  
achievement_status | GoalAchievementStatus  
priority | GoalPriority  
due_date | Date  
progress | String  
##  Enumeration types 
###  GoalLifecycleStatus 
Name | Value  
---|---  
PROPOSED | proposed  
PLANNED | planned  
ACCEPTED | accepted  
ACTIVE | active  
ON_HOLD | on-hold  
COMPLETED | completed  
CANCELLED | cancelled  
REJECTED | rejected  
###  GoalAchievementStatus 
Name | Value  
---|---  
IN_PROGRESS | in-progress  
IMPROVING | improving  
WORSENING | worsening  
NO_CHANGE | no-change  
ACHIEVED | achieved  
SUSTAINING | sustaining  
NOT_ACHIEVED | not-achieved  
NO_PROGRESS | no-progress  
NOT_ATTAINABLE | not-attainable  
###  GoalPriority 
Name | Value  
---|---  
HIGH | high-priority  
MEDIUM | medium-priority  
LOW | low-priority  
----- END PAGE https://docs.canvasmedical.com/sdk/data-goal/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-history-present-illness/
##  Introduction 
The `HistoryOfPresentIllness` model represents a History of Present Illness (HPI) recorded on a Note, and is always associated with a Note and a Patient. It is the data model behind the `hpi` command.
##  Basic usage 
To get an HPI by identifier, use the `get` method on the `HistoryOfPresentIllness` model manager:
    ```python
    from canvas_sdk.v1.data.history_present_illness import HistoryOfPresentIllness
    hpi = HistoryOfPresentIllness.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    ```
If you have a patient object, or note object, the histories of present illness for a patient or note can be accessed with the `histories_of_present_illness` attribute on a `Patient` or `Note` object:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    from canvas_sdk.v1.data.note import Note
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    histories = patient.histories_of_present_illness.all()
    note = Note.objects.get(id="89992c23-c298-4118-864a-26cb3e1ae822")
    histories = note.histories_of_present_illness.all()
    ```
##  Reading the narrative 
The HPI text is stored as a structured document in `narrative_json`. The `narrative` property renders it as plain text, so that is the field to read:
    ```python
    from canvas_sdk.v1.data.history_present_illness import HistoryOfPresentIllness
    hpi = HistoryOfPresentIllness.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    text = hpi.narrative
    ```
##  Filtering 
Histories of present illness can be filtered by any column on the model. Note that `narrative` is a Python property rather than a column, so it cannot be used in `filter()` — filter on `narrative_json` instead.
###  Committed histories of present illness 
The `committed` method returns records that have been committed and not entered in error:
    ```python
    from canvas_sdk.v1.data.history_present_illness import HistoryOfPresentIllness
    committed_histories = HistoryOfPresentIllness.objects.committed()
    ```
##  Attributes 
###  HistoryOfPresentIllness 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
originator | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
patient | [Patient](/sdk/data-patient/#patient)  
note | [Note](/sdk/data-note)  
narrative_json | JSON  
narrative | String (computed)  
----- END PAGE https://docs.canvasmedical.com/sdk/data-history-present-illness/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-imaging-report-template/
##  Introduction 
The `ImagingReportTemplate`, `ImagingReportTemplateField`, and `ImagingReportTemplateFieldOption` models represent the templates used for imaging reports. Templates define the structure of an imaging report, including what fields need to be filled in and what options are available for each field.
##  Basic Usage 
To retrieve an `ImagingReportTemplate` by identifier, use the `get` method on the model manager:
    ```python
    from canvas_sdk.v1.data.imaging import ImagingReportTemplate
    template = ImagingReportTemplate.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    ```
To access the fields defined in a template:
    ```python
    from canvas_sdk.v1.data.imaging import ImagingReportTemplate
    template = ImagingReportTemplate.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    fields = template.fields.all()
    ```
##  Filtering 
Templates can be filtered by any attribute on the models.
###  By active status 
    ```python
    from canvas_sdk.v1.data.imaging import ImagingReportTemplate
    active_templates = ImagingReportTemplate.objects.active()
    ```
###  By type 
    ```python
    from canvas_sdk.v1.data.imaging import ImagingReportTemplate
    # Get custom (user-created) templates
    custom = ImagingReportTemplate.objects.custom()
    # Get built-in (system) templates
    builtin = ImagingReportTemplate.objects.builtin()
    ```
###  By search 
    ```python
    from canvas_sdk.v1.data.imaging import ImagingReportTemplate
    results = ImagingReportTemplate.objects.search("chest x-ray")
    ```
##  Attributes 
###  ImagingReportTemplate 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
name | String  
long_name | String  
code | String  
code_system | String  
search_keywords | String  
active | Boolean  
custom | Boolean  
rank | Integer  
fields | ImagingReportTemplateField[]  
###  ImagingReportTemplateField 
Field Name | Type  
---|---  
dbid | Integer  
report_template | ImagingReportTemplate  
sequence | Integer  
code | String  
code_system | String  
label | String  
units | String  
type | String  
required | Boolean  
options | ImagingReportTemplateFieldOption[]  
###  ImagingReportTemplateFieldOption 
Field Name | Type  
---|---  
dbid | Integer  
field | ImagingReportTemplateField  
label | String  
key | String  
----- END PAGE https://docs.canvasmedical.com/sdk/data-imaging-report-template/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-imaging/
##  Introduction 
The `ImagingOrder`, `ImagingReview`, `ImagingReport`, and `ImagingReportCoding` models represent imaging results.
##  Basic Usage 
To retrieve an `ImagingOrder`, `ImagingReview`, or `ImagingReport` by identifier, use the `get` method on the model manager:
    ```python
    from canvas_sdk.v1.data.imaging import ImagingOrder, ImagingReview, ImagingReport
    imaging_order = ImagingOrder.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    imaging_review = ImagingReview.objects.get(id="c02c6b02-2581-46bf-819c-b5aacad2134c")
    imaging_report = ImagingReport.objects.get(id="c1a5a35a-4ee2-4a0e-85c0-21739dc8c4a8")
    ```
If you have a patient object, the orders, reviews, and reports can be accessed with the `imaging_orders`, `imaging_reviews`, and `imaging_results` attributes, respectively on a `Patient` object:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    orders = patient.imaging_orders.all()
    reviews = patient.imaging_reviews.all()
    reports = patient.imaging_results.all()
    ```
##  Filtering 
Imaging orders, reviews, and reports can be filtered by any attribute that exists on the models.
Filtering is done with the `filter` method on the `ImagingOrder`, `ImagingReview`, and `ImagingReport` model managers.
###  By attribute 
Specify an attribute with `filter` to filter by that attribute:
    ```python
    from canvas_sdk.v1.data.imaging import ImagingOrder, ImagingReview, ImagingReport
    orders = ImagingOrder.objects.filter(status="completed")
    reviews = ImagingReview.objects.filter(is_released_to_patient=False)
    reports = ImagingReport.objects.filter(requires_signature=True)
    ```
###  By ValueSet 
See [Value Sets](/sdk/data-value-sets/) for the library of built-in value sets and how to create your own.
`ImagingReport` supports `ValueSet` filtering through the `find` method on its model manager:
    ```python
    from canvas_sdk.v1.data.imaging import ImagingReport
    from canvas_sdk.value_set.v2022.diagnostic_study import Mammography
    reports = ImagingReport.objects.find(Mammography)
    ```
`find` joins through the report's `codings` reverse relation and matches on `(system, code)` pairs from the value set, so a coding must match both the code system and the code to be included.
###  Committed records 
The `committed` method returns `ImagingOrder` and `ImagingReview` records that have been committed and not entered in error:
    ```python
    from canvas_sdk.v1.data.imaging import ImagingOrder, ImagingReview
    committed_orders = ImagingOrder.objects.committed()
    committed_reviews = ImagingReview.objects.committed()
    ```
##  Related Tasks 
To retrieve an Imaging Order's related tasks, use the `get_task_objects` method on the ImagingOrder object.
    ```python
    from canvas_sdk.v1.data.imaging import ImagingOrder
    imaging_order = ImagingOrder.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    tasks = imaging_order.get_task_objects().all()
    ```
The `task_list` computed property returns the same related tasks as a `list[Task]`:
    ```python
    from canvas_sdk.v1.data.imaging import ImagingOrder
    imaging_order = ImagingOrder.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    tasks = imaging_order.task_list
    ```
##  Accessing the report file 
The `document_url` property on `ImagingReport` returns a presigned S3 URL for securely accessing the report's file. The URL is valid for one hour and is regenerated on each access, so don't persist or cache it. If the report has no associated file, `document_url` returns `None`.
    ```python
    from canvas_sdk.v1.data.imaging import ImagingReport
    imaging_report = ImagingReport.objects.get(id="c1a5a35a-4ee2-4a0e-85c0-21739dc8c4a8")
    # Presigned S3 URL to the report file, or None if the report has no file
    url = imaging_report.document_url
    ```
##  The document reference 
A report that has a file also has a [DocumentReference](/sdk/data-document-reference/#the-related-object) pointing back at it — the record that carries the report's document coding, category, and status, and that represents it in the FHIR API. `document_url` above is the direct route to the file itself; reach for the document reference when you want that surrounding metadata.
Resolve the [ContentType](/sdk/data-content-type/) at runtime from its stable `app_label` and `model` — never hardcode the per-environment `dbid` — and match `object_id` against the report's `dbid`:
    ```python
    from canvas_sdk.v1.data import ContentType, DocumentReference, ImagingReport
    imaging_report = ImagingReport.objects.get(id="c1a5a35a-4ee2-4a0e-85c0-21739dc8c4a8")
    content_type = ContentType.objects.filter(app_label="api", model="imagingreport").first()
    document = DocumentReference.objects.filter(
        content_type=content_type, object_id=imaging_report.dbid
    ).first()
    ```
> **Info:** `object_id` holds the related record's integer `dbid`, not its UUID `id`. A report with no file has no document reference, so handle `None`. 
##  Attributes 
###  ImagingOrder 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
originator | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
patient | [Patient](/sdk/data-patient/#patient)  
note | [Note](/sdk/data-note/#note)  
imaging | String  
imaging_center | [ServiceProvider](/sdk/data-serviceprovider/#service-provider)  
note_to_radiologist | String  
internal_comment | String  
status | [OrderStatus](/sdk/data-enumeration-types/#orderstatus)  
date_time_ordered | DateTime  
ordering_provider | [Staff](/sdk/data-staff/#staff)  
priority | String  
delegated | Boolean  
task_ids | String  
results | ImagingReport[]  
###  ImagingReview 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
originator | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
patient_communication_method | [ReviewPatientCommunicationMethod](/sdk/data-enumeration-types/#reviewpatientcommunicationmethod)  
internal_comment | String  
message_to_patient | String  
is_released_to_patient | Boolean  
status | [ReviewStatus](/sdk/data-enumeration-types/#reviewstatus)  
note | [Note](/sdk/data-note/#note)  
patient | [Patient](/sdk/data-patient/#patient)  
###  ImagingReport 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
review_mode | [DocumentReviewMode](/sdk/data-enumeration-types/#documentreviewmode)  
junked | Boolean  
requires_signature | Boolean  
assigned_date | DateTime  
patient | [Patient](/sdk/data-patient/#patient)  
order | ImagingOrder  
source | ImagingReportSource  
name | String  
result_date | Date  
original_date | Date  
review | ImagingReview  
document_url | String (property) — presigned S3 URL, or `None` if the report has no file  
codings | ImagingReportCoding[]  
###  ImagingReportCoding 
Field Name | Type  
---|---  
dbid | Integer  
report | ImagingReport  
system | String  
version | String  
code | String  
display | String  
user_selected | Boolean  
value | String  
##  Enumeration types 
###  ImagingReportSource 
Value | Label  
---|---  
RADIOLOGY_PATIENT | Radiology Report From Patient  
VERBAL_PATIENT | Verbal Report From Patient  
DIRECTLY_RADIOLOGY | Directly Radiology Report  
----- END PAGE https://docs.canvasmedical.com/sdk/data-imaging/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-immunization/
##  Introduction 
The `Immunization` model represents a record of immunization events and immunization statements for a patient. Immunizations can be actively administered medications or historical records of immunizations received elsewhere. The `ImmunizationStatement` model represents historical immunization records and vaccination history.
##  Basic usage 
To get an immunization by identifier, use the `get` method on the `Immunization` model manager:
    ```python
    from canvas_sdk.v1.data.immunization import Immunization
    immunization = Immunization.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    ```
If you have a patient object, the immunizations for a patient can be accessed with the `immunizations` attribute on a `Patient` object:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    immunizations = patient.immunizations.all()
    ```
If you have a patient ID, you can get the immunizations for the patient with the `for_patient` method on the `Immunization` model manager:
    ```python
    from canvas_sdk.v1.data.immunization import Immunization
    patient_id = "1eed3ea2a8d546a1b681a2a45de1d790"
    immunizations = Immunization.objects.for_patient(patient_id)
    ```
##  Codings 
The codings for an immunization can be accessed with the `codings` attribute on an `Immunization` object:
    ```python
    from canvas_sdk.v1.data.immunization import Immunization
    from logger import log
    immunization = Immunization.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    for coding in immunization.codings.all():
        log.info(f"system:  {coding.system}")
        log.info(f"code:    {coding.code}")
        log.info(f"display: {coding.display}")
    ```
##  Filtering 
Immunizations can be filtered by any attribute that exists on the model.
Filtering for immunizations is done with the `filter` method on the `Immunization` model manager.
###  By attribute 
Specify an attribute with `filter` to filter by that attribute:
    ```python
    from canvas_sdk.v1.data.immunization import Immunization
    immunizations = Immunization.objects.filter(status="completed")
    ```
###  By ValueSet 
See [Value Sets](/sdk/data-value-sets/) for the library of built-in value sets and how to create your own.
Filtering by ValueSet works a little differently. The `find` method on the model manager is used to perform `ValueSet` filtering:
    ```python
    from canvas_sdk.v1.data.immunization import Immunization
    from canvas_sdk.value_set.v2022.immunization import InfluenzaVaccine
    immunizations = Immunization.objects.find(InfluenzaVaccine)
    ```
`find` also works on the `ImmunizationStatement` model manager, matching against the statement's own coding records, which it exposes through the singular `coding` accessor (unlike `Immunization.codings`):
    ```python
    from canvas_sdk.v1.data.immunization import ImmunizationStatement
    from canvas_sdk.value_set.v2022.immunization import InfluenzaVaccine
    immunization_statements = ImmunizationStatement.objects.find(InfluenzaVaccine)
    ```
###  Committed and active records 
The `committed` method returns immunizations that have been committed and not entered in error. The `active` method is an alias for `committed` and returns the same records:
    ```python
    from canvas_sdk.v1.data.immunization import Immunization
    committed_immunizations = Immunization.objects.committed()
    active_immunizations = Immunization.objects.active()
    ```
The same methods are available on the `ImmunizationStatement` model manager:
    ```python
    from canvas_sdk.v1.data.immunization import ImmunizationStatement
    committed_statements = ImmunizationStatement.objects.committed()
    active_statements = ImmunizationStatement.objects.active()
    ```
##  Immunization Statements 
To work with immunization statements (historical records), use the `ImmunizationStatement` model:
    ```python
    from canvas_sdk.v1.data.immunization import ImmunizationStatement
    patient_id = "1eed3ea2a8d546a1b681a2a45de1d790"
    immunization_statements = ImmunizationStatement.objects.for_patient(patient_id)
    ```
##  Attributes 
###  Immunization 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
patient | [Patient](/sdk/data-patient/#patient)  
note | [Note](/sdk/data-note/#note)  
status | ImmunizationStatus  
lot_number | String  
manufacturer | String  
exp_date_original | String  
exp_date | Date  
sig_original | String  
date_ordered | Date  
given_by | [Staff](/sdk/data-staff/#staff)  
consent_given | Boolean  
take_quantity | Float  
dose_form | String  
route | String  
frequency_normalized_per_day | Float  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
originator | [CanvasUser](/sdk/data-canvasuser)  
created | DateTime  
modified | DateTime  
codings | ImmunizationCoding[]  
###  ImmunizationCoding 
Field Name | Type  
---|---  
dbid | Integer  
system | String  
version | String  
code | String  
display | String  
user_selected | Boolean  
immunization | Immunization  
###  ImmunizationStatement 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
patient | [Patient](/sdk/data-patient/#patient)  
note | [Note](/sdk/data-note/#note)  
date_original | String  
date | Date  
evidence | String  
comment | String  
reason_not_given | ImmunizationReasonsNotGiven  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
originator | [CanvasUser](/sdk/data-canvasuser)  
created | DateTime  
modified | DateTime  
coding | ImmunizationStatementCoding[]  
###  ImmunizationStatementCoding 
Field Name | Type  
---|---  
dbid | Integer  
system | String  
version | String  
code | String  
display | String  
user_selected | Boolean  
immunization_statement | ImmunizationStatement  
##  Enumeration types 
###  ImmunizationStatus 
Value | Label  
---|---  
in-progress | In Progress  
on-hold | on-hold  
completed | completed  
stopped | stopped  
###  ImmunizationReasonsNotGiven 
Value | Label  
---|---  
NA | not applicable  
IMMUNE | immunity  
MEDPREC | medical precaution  
OSTOCK | product out of stock  
PATOBJ | patient objection  
----- END PAGE https://docs.canvasmedical.com/sdk/data-immunization/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-instruction/
##  Introduction 
The `Instruction` model represents an `Instruct` command in a patient's note — for example, "cessation of smoking" counseling, dietary instructions, or any other piece of clinical guidance recorded as an Instruct command. Instructions are included regardless of command state (staged or committed); use `.committed()` to filter to only committed commands.
Querying `Instruction` from a plugin is the recommended way to ask "has this patient been given an instruction in this value set?" — for example, when computing quality measures that look for tobacco cessation counseling or dialysis education.
##  Basic usage 
To get an instruction by identifier, use the `get` method on the `Instruction` model manager:
    ```python
    from canvas_sdk.v1.data.instruction import Instruction
    instruction = Instruction.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    ```
If you have a patient object, the instructions for a patient can be accessed with the `instructions` attribute on a `Patient` object:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    # Returns all instructions for the patient, regardless of command state (staged or committed)
    instructions = patient.instructions.all()
    ```
If you have a patient ID, you can get the instructions for the patient with the `for_patient` method on the `Instruction` model manager:
    ```python
    from canvas_sdk.v1.data.instruction import Instruction
    patient_id = "1eed3ea2a8d546a1b681a2a45de1d790"
    # All instructions for the patient, regardless of command state (staged or committed)
    instructions = Instruction.objects.for_patient(patient_id)
    # Only committed instructions for the patient
    committed_instructions = Instruction.objects.for_patient(patient_id).committed()
    ```
##  Codings 
The codings for an instruction can be accessed with the `codings` attribute on an `Instruction` object:
    ```python
    from canvas_sdk.v1.data.instruction import Instruction
    from logger import log
    instruction = Instruction.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    for coding in instruction.codings.all():
        log.info(f"system:  {coding.system}")
        log.info(f"code:    {coding.code}")
        log.info(f"display: {coding.display}")
    ```
Instruct commands originated through the SDK use either the SNOMED CT code system or an internal "unstructured" system for free-text instructions. See the [InstructCommand](/sdk/commands/#instruct) effect for the write-side details.
##  Committed instructions 
The `committed` method returns instructions whose underlying command has been committed and not entered in error:
    ```python
    from canvas_sdk.v1.data.instruction import Instruction
    committed_instructions = Instruction.objects.committed()
    ```
##  Filtering 
Instructions can be filtered by any attribute that exists on the model.
Filtering for instructions is done with the `filter` method on the `Instruction` model manager.
###  By attribute 
Specify an attribute with `filter` to filter by that attribute:
    ```python
    from canvas_sdk.v1.data.instruction import Instruction
    instructions = Instruction.objects.filter(note__id="2c91b0d8-7b9d-4ef1-89e2-1f9a3a8a2b14")
    ```
###  By ValueSet 
See [Value Sets](/sdk/data-value-sets/) for the library of built-in value sets and how to create your own.
Filtering by ValueSet works a little differently. The `find` method on the model manager is used to perform `ValueSet` filtering:
    ```python
    from canvas_sdk.v1.data.instruction import Instruction
    from canvas_sdk.value_set.v2022.intervention import TobaccoUseCessationCounseling
    cessation_counseling = (
        Instruction.objects
        .for_patient("1eed3ea2a8d546a1b681a2a45de1d790")
        .committed()
        .find(TobaccoUseCessationCounseling)
    )
    ```
`find` joins through the `codings` reverse relation and filters on `(system, code)` pairs from the value set, so it composes naturally with `for_patient` and `committed`.
##  Attributes 
###  Instruction 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
originator | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
patient | [Patient](/sdk/data-patient/#patient)  
note | [Note](/sdk/data-note/#note)  
narrative | String  
codings | InstructionCoding[]  
###  InstructionCoding 
Field Name | Type  
---|---  
dbid | Integer  
system | String  
version | String  
code | String  
display | String  
user_selected | Boolean  
instruction | Instruction  
----- END PAGE https://docs.canvasmedical.com/sdk/data-instruction/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-integration-task/
##  Introduction 
The `IntegrationTask` and `IntegrationTaskReview` models represent incoming documents that need processing in Canvas. Integration tasks are created when documents arrive via fax, document upload, integration engine, or the patient portal. Each task can have one or more reviews that track who is responsible for processing the document and its current state.
##  Basic Usage 
To retrieve an `IntegrationTask` or `IntegrationTaskReview` by identifier, use the `get` method on the model manager:
    ```python
    from canvas_sdk.v1.data.integration_task import IntegrationTask, IntegrationTaskReview
    task = IntegrationTask.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    review = IntegrationTaskReview.objects.get(id="c1a5a35a-4ee2-4a0e-85c0-21739dc8c4a8")
    ```
If you have a patient object, integration tasks can be accessed with the `integration_tasks` attribute on a `Patient` object:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    tasks = patient.integration_tasks.all()
    ```
##  Filtering 
Integration tasks and reviews can be filtered by any attribute that exists on the models.
###  By status 
Filter tasks by their processing status:
    ```python
    from canvas_sdk.v1.data.integration_task import IntegrationTask
    # Get all unread tasks
    unread = IntegrationTask.objects.unread()
    # Get tasks pending review (UNREAD or READ)
    pending = IntegrationTask.objects.pending_review()
    # Get processed tasks (PROCESSED or REVIEWED)
    processed = IntegrationTask.objects.processed()
    # Get tasks with errors
    errored = IntegrationTask.objects.with_errors()
    # Get non-junked tasks
    active = IntegrationTask.objects.not_junked()
    ```
###  By channel 
Filter tasks by their source channel:
    ```python
    from canvas_sdk.v1.data.integration_task import IntegrationTask
    faxes = IntegrationTask.objects.faxes()
    uploads = IntegrationTask.objects.uploads()
    engine_tasks = IntegrationTask.objects.from_integration_engine()
    portal_tasks = IntegrationTask.objects.from_patient_portal()
    ```
###  By patient 
    ```python
    from canvas_sdk.v1.data.integration_task import IntegrationTask
    tasks = IntegrationTask.objects.for_patient("patient-id")
    ```
###  Filtering reviews 
    ```python
    from canvas_sdk.v1.data.integration_task import IntegrationTaskReview
    # Get reviews for a specific task
    reviews = IntegrationTaskReview.objects.for_task("task-id")
    # Get active (non-junked) reviews
    active_reviews = IntegrationTaskReview.objects.active()
    # Get reviews by a specific reviewer
    reviewer_reviews = IntegrationTaskReview.objects.by_reviewer("staff-id")
    # Get reviews assigned to a specific team
    team_reviews = IntegrationTaskReview.objects.by_team("team-id")
    ```
##  Attributes 
###  IntegrationTask 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
status | IntegrationTaskStatus  
type | String  
title | String  
channel | IntegrationTaskChannel  
patient | [Patient](/sdk/data-patient/#patient)  
service_provider | [ServiceProvider](/sdk/data-serviceprovider/#service-provider)  
reviews | IntegrationTaskReview[]  
####  Properties 
Property | Type | Description  
---|---|---  
is_fax | Boolean | Whether this is a fax task  
is_pending | Boolean | Whether this task is pending review  
is_processed | Boolean | Whether this task has been processed  
has_error | Boolean | Whether this task has an error  
is_junked | Boolean | Whether this task is junked  
###  IntegrationTaskReview 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
task | IntegrationTask  
template_name | String  
document_key | String  
reviewer | [Staff](/sdk/data-staff/#staff)  
team_reviewer | [Team](/sdk/data-team/#team)  
junked | Boolean  
####  Properties 
Property | Type | Description  
---|---|---  
is_active | Boolean | Whether this review is active (not junked)  
##  Enumeration types 
###  IntegrationTaskStatus 
Value | Label  
---|---  
UNR | Unread  
UER | Unread Error  
REA | Read  
ERR | Error  
PRO | Processed  
REV | Reviewed  
JUN | Junk  
###  IntegrationTaskChannel 
Value | Label  
---|---  
fax | Fax  
document_upload | Document Upload  
from_integration_engine | From Integration Engine  
from_patient_portal | From Patient Portal  
----- END PAGE https://docs.canvasmedical.com/sdk/data-integration-task/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-invoice/
##  Introduction 
The `Invoice` model represents a statement generated for a patient or their guarantor — who it was addressed to, what it totals, how it was sent, and where it stands.
Invoices are produced by Canvas billing workflows rather than by plugins: automated statement runs, batch runs, and one-off statements each record their origin in `workflow`.
> **Info:** Invoice records have no UUID `id` — they are identified by their integer `dbid`. 
##  Basic Usage 
    ```python
    from canvas_sdk.v1.data import Invoice
    invoice = Invoice.objects.get(dbid=42)
    ```
If you have a `Patient` object, the statements addressed to them are available through the `invoices` reverse relation:
    ```python
    from canvas_sdk.v1.data import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    invoices = patient.invoices.all()
    ```
A [Claim](/sdk/data-claim/#claim) points at the most recent statement it appeared on:
    ```python
    from canvas_sdk.v1.data import Claim
    claim = Claim.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    invoice = claim.latest_invoice
    ```
##  Filtering 
    ```python
    from canvas_sdk.v1.data.invoice import Invoice, InvoiceStatus, InvoiceWorkflow
    # Active statements only
    active = Invoice.objects.filter(status=InvoiceStatus.ACTIVE)
    # Statements a staff member generated one at a time, rather than by a batch or automated run
    adhoc = Invoice.objects.filter(workflow=InvoiceWorkflow.ADHOC)
    ```
`Invoice` is addressed through `recipient` rather than a `patient` field, so filter on `recipient` to scope to one patient:
    ```python
    from canvas_sdk.v1.data import Invoice
    invoices = Invoice.objects.filter(recipient__id="1eed3ea2a8d546a1b681a2a45de1d790")
    ```
##  Accessing the statement PDF 
`Invoice` holds the statement's amounts and delivery details, not the rendered file. Canvas attaches the PDF to a [DocumentReference](/sdk/data-document-reference/#the-related-object), which you reach by resolving the [ContentType](/sdk/data-content-type/) for the invoice and matching `object_id` against the invoice's `dbid`:
    ```python
    from canvas_sdk.v1.data import ContentType, DocumentReference, Invoice
    invoice = Invoice.objects.get(dbid=42)
    content_type = ContentType.objects.filter(
        app_label="quality_and_revenue", model="invoicefull"
    ).first()
    document = DocumentReference.objects.filter(
        content_type=content_type, object_id=invoice.dbid
    ).first()
    url = document.document_url if document else None
    ```
##  Attributes 
###  Invoice 
Field Name | Type  
---|---  
dbid | Integer  
originator | [CanvasUser](/sdk/data-canvasuser/)  
recipient | [Patient](/sdk/data-patient/#patient)  
recipient_type | InvoiceRecipients  
total_amount | Decimal  
status | InvoiceStatus  
workflow | InvoiceWorkflow  
error_message | String  
sent_mean | InvoiceSentMeans  
`error_message` carries the reason a statement failed to go out, and is empty for statements that did not fail.
##  Enumeration types 
###  InvoiceRecipients 
Who the statement was addressed to.
Value | Label  
---|---  
patient | Patient  
guarantor | Guarantor  
###  InvoiceStatus 
Value | Label  
---|---  
active | Active  
error | Error  
archived | Archived  
###  InvoiceWorkflow 
How the statement was produced.
Value | Label  
---|---  
automated | Automated  
adhoc | Adhoc  
batch | Batch  
###  InvoiceSentMeans 
Value | Label  
---|---  
mail | Mail  
e-mail | E-mail
----- END PAGE https://docs.canvasmedical.com/sdk/data-invoice/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-lab-partner-and-test/
##  Introduction 
The **LabPartner** , **LabPartnerTest** , **LabPartnerTestQuestion** , and **LabPartnerTestQuestionChoice** models represent external lab partners, the tests they offer, and the ask-at-order-entry (AOE) questions associated with each test within Canvas.
* * *
##  LabPartner 
The `LabPartner` model stores information about a lab partner
###  Basic Usage 
To retrieve a lab partner by its unique identifier:
    ```python
    from canvas_sdk.v1.data.lab import LabPartner
    lab_partner = LabPartner.objects.get(id="your-uuid-here")
    ```
You can also filter lab partners by attributes. For example, to list all active lab partners:
    ```python
    from canvas_sdk.v1.data.lab import LabPartner
    active_lab_partners = LabPartner.objects.filter(active=True)
    ```
##  LabPartnerTest 
The `LabPartnerTest` model represents a test offered by a lab partner. Each test is linked to a lab partner via a foreign key.
###  Basic Usage 
To retrieve tests for a given lab partner, you can access the related tests using the reverse relationship:
    ```python
    from canvas_sdk.v1.data.lab import LabPartner
    lab_partner = LabPartner.objects.get(id="your-uuid-here")
    tests = lab_partner.available_tests.all()
    ```
Alternatively, you can directly filter tests by attributes:
    ```python
    from canvas_sdk.v1.data.lab import LabPartnerTest
    tests_with_code = LabPartnerTest.objects.filter(order_code="XYZ123")
    ```
##  Attributes 
###  LabPartner 
Field Name | Type | Description  
---|---|---  
id | UUID | The universally unique identifier for the lab partner.  
dbid | Integer | The internal database identifier (primary key) for the lab partner.  
name | String | The name of the lab partner.  
active | Boolean | Indicates whether the lab partner is currently active.  
electronic_ordering_enabled | Boolean | Indicates if electronic ordering is enabled for this lab partner.  
keywords | Text | Keywords associated with the lab partner.  
default_lab_account_number | String | The default lab account number used for orders.  
available_tests | LabPartnerTest[] | The tests offered by this lab partner (reverse relation, accessible via `available_tests`).  
###  LabPartnerTest Attributes 
Field Name | Type | Description  
---|---|---  
id | UUID | The universally unique identifier for the test record.  
dbid | Integer | The internal database identifier (primary key) for the test record.  
lab_partner | LabPartner | A reference to the related `LabPartner` (accessible via the related name `available_tests`).  
order_code | String | A code used to identify the test order. May be blank.  
order_name | Text | The name of the test order.  
keywords | Text | Keywords associated with the test. May be blank.  
cpt_code | String | The CPT code for the test, if available. Can be blank or null.  
questions | LabPartnerTestQuestion[] | AOE questions associated with this test.  
* * *
##  LabPartnerTestQuestion 
The `LabPartnerTestQuestion` model represents an ask-at-order-entry (AOE) question associated with a lab partner test. AOE questions are prompts that must be answered when ordering a specific lab test (e.g., "Is the patient fasting?", "Source of specimen").
###  Basic Usage 
To retrieve questions for a given lab partner test:
    ```python
    from canvas_sdk.v1.data.lab import LabPartnerTest
    test = LabPartnerTest.objects.get(id="your-uuid-here")
    questions = test.questions.all()
    ```
To filter for required questions only:
    ```python
    required_questions = test.questions.filter(required=True)
    ```
To directly query questions by code:
    ```python
    from canvas_sdk.v1.data.lab import LabPartnerTestQuestion
    questions = LabPartnerTestQuestion.objects.filter(code="FAST")
    ```
* * *
##  LabPartnerTestQuestionChoice 
The `LabPartnerTestQuestionChoice` model represents a selectable answer option for an AOE question. Not all questions have predefined choices (e.g., free-text questions may have none).
###  Basic Usage 
To retrieve choices for a given question:
    ```python
    question = test.questions.first()
    choices = question.choices.all()
    ```
###  Example: Building AOE prompts for a lab test 
    ```python
    from canvas_sdk.v1.data.lab import LabPartnerTest
    from logger import log
    test = LabPartnerTest.objects.get(id="your-uuid-here")
    for question in test.questions.all():
        log.info(f"Question: {question.body} (required={question.required})")
        for choice in question.choices.all():
            log.info(f"  - {choice.label}: {choice.value}")
    ```
##  Attributes 
###  LabPartnerTestQuestion Attributes 
Field Name | Type | Description  
---|---|---  
dbid | Integer | The internal database identifier (primary key) for the question.  
lab_partner_test | LabPartnerTest | A reference to the related `LabPartnerTest` (accessible via the related name `questions`).  
required | Boolean | Whether this question must be answered when ordering the test.  
code | String | A code identifying the question (e.g., "FAST" for fasting status).  
body | Text | The full text of the question displayed to the user.  
type | String | The question type (e.g., "text", "select", "date", "numeric").  
created | DateTime | When the record was created.  
modified | DateTime | When the record was last modified.  
choices | LabPartnerTestQuestionChoice[] | Selectable answer options for this question.  
###  LabPartnerTestQuestionChoice Attributes 
Field Name | Type | Description  
---|---|---  
dbid | Integer | The internal database identifier (primary key) for the choice.  
lab_partner_test_question | LabPartnerTestQuestion | A reference to the related `LabPartnerTestQuestion` (accessible via the related name `choices`).  
label | String | The display label for this choice (shown to the user).  
value | String | The value submitted when this choice is selected.  
created | DateTime | When the record was created.  
modified | DateTime | When the record was last modified.  
----- END PAGE https://docs.canvasmedical.com/sdk/data-lab-partner-and-test/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-lab-report-template/
##  Introduction 
The `LabReportTemplate`, `LabReportTemplateField`, and `LabReportTemplateFieldOption` models represent the templates used for point-of-care (POC) labs and custom lab reports. Templates define the structure of a lab report, including what fields need to be filled in and what options are available for each field.
##  Basic Usage 
To retrieve a `LabReportTemplate` by identifier, use the `get` method on the model manager:
    ```python
    from canvas_sdk.v1.data.lab import LabReportTemplate
    template = LabReportTemplate.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    ```
To access the fields defined in a template:
    ```python
    from canvas_sdk.v1.data.lab import LabReportTemplate
    template = LabReportTemplate.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    fields = template.fields.all()
    ```
##  Filtering 
Templates can be filtered by any attribute on the models.
###  By active status 
    ```python
    from canvas_sdk.v1.data.lab import LabReportTemplate
    # Get all active templates
    active_templates = LabReportTemplate.objects.active()
    # Get inactive templates
    inactive_templates = LabReportTemplate.objects.inactive()
    ```
###  By type 
    ```python
    from canvas_sdk.v1.data.lab import LabReportTemplate
    # Get custom (user-created) templates
    custom = LabReportTemplate.objects.custom()
    # Get built-in (system) templates
    builtin = LabReportTemplate.objects.builtin()
    # Get point-of-care test templates
    poc = LabReportTemplate.objects.point_of_care()
    ```
###  By search 
    ```python
    from canvas_sdk.v1.data.lab import LabReportTemplate
    results = LabReportTemplate.objects.search("glucose")
    ```
##  Attributes 
###  LabReportTemplate 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
name | String  
code | String  
code_system | String  
search_keywords | String  
active | Boolean  
custom | Boolean  
poc | Boolean  
fields | LabReportTemplateField[]  
###  LabReportTemplateField 
Field Name | Type  
---|---  
dbid | Integer  
report_template | LabReportTemplate  
sequence | Integer  
code | String  
code_system | String  
label | String  
units | String  
type | FieldType  
required | Boolean  
options | LabReportTemplateFieldOption[]  
###  LabReportTemplateFieldOption 
Field Name | Type  
---|---  
dbid | Integer  
field | LabReportTemplateField  
label | String  
key | String  
##  Enumeration types 
###  FieldType 
Value | Label  
---|---  
float | Float  
select | Select  
text | Text  
checkbox | Checkbox  
radio | Radio  
array | Array  
labReport | Lab Report  
remoteFields | Remote Fields  
autocomplete | Autocomplete  
date | Date  
----- END PAGE https://docs.canvasmedical.com/sdk/data-lab-report-template/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-labs/
##  Introduction 
The Canvas SDK provides comprehensive models for working with laboratory data throughout the entire lab workflow—from ordering tests to reviewing results. The primary models include:
  - **`LabOrder`** : Represents a lab order placed for a patient, including order details, transmission type, and associated tests
  - **`LabTest`** : Individual tests within a lab order, tracking status from creation through processing
  - **`LabReport`** : Contains the results returned from the lab, including all values and associated metadata
  - **`LabReportRemark`** : Report-level remarks from lab personnel, accessible via `LabReport.remarks`
  - **`LabValue`** : Individual test results within a lab report, including values, units, and reference ranges
  - **`LabReview`** : Tracks the clinical review process for lab results, including provider comments and patient communication
  - **`DiagnosticReport`** : The `DiagnosticReport` linked to a `LabReport`, accessible via `LabReport.diagnostic_reports`
##  Basic Usage 
To retrieve a `LabReport` model by id, use the `objects.get` method on the model. For example:
    ```python
    from canvas_sdk.v1.data.lab import LabReport
    lab_report = LabReport.objects.get(id="bcd287b7-8b04-4540-a1ea-6529eb576565")
    ```
##  Filtering 
To retrieve the `LabValue` instances that are associated with the `LabReport`, you can either call the `values` on the `LabReport` instance:
    ```python
    from canvas_sdk.v1.data.lab import LabReport
    lab_report = LabReport.objects.get(id="bcd287b7-8b04-4540-a1ea-6529eb576565")
    lab_values = lab_report.values.all()
    ```
Or query the `LabValue` model and pass the `report` argument:
    ```python
    from canvas_sdk.v1.data.lab import LabReport, LabValue
    lab_report = LabReport.objects.get(id="bcd287b7-8b04-4540-a1ea-6529eb576565")
    lab_values = LabValue.objects.filter(lab_report=lab_report)
    ```
Additionally, codings for lab values can be attained by querying the `LabValueCoding` model. To retrieve the codings associated with a `LabValue`, you can call `codings` on the `LabValue` instance:
    ```python
    from logger import log
    from canvas_sdk.v1.data.lab import LabReport, LabValue
    lab_report = LabReport.objects.get(id="bcd287b7-8b04-4540-a1ea-6529eb576565")
    lab_values = LabValue.objects.filter(lab_report=lab_report)
    for value in lab_values:
        log.info(value.codings.all())
    ```
Or query the `LabValueCoding` model directly:
    ```python
    from logger import log
    from canvas_sdk.v1.data.lab import LabReport, LabValue, LabValueCoding
    lab_report = LabReport.objects.get(id="bcd287b7-8b04-4540-a1ea-6529eb576565")
    lab_values = LabValue.objects.filter(lab_report=lab_report)
    for value in lab_values:
        lab_value_codings = LabValueCoding.objects.filter(value=value)
        log.info(lab_value_codings)
    ```
###  Ordered vs. result tests 
A `LabReport` references two kinds of `LabTest` rows, and `LabReport` exposes each as its own property:
  - **`ordered_tests`** : `LabTest` rows created when a `LabOrder` is placed. These represent the tests that were requested and are not associated with any `LabValue` records.
  - **`result_tests`** : `LabTest` rows created for the results themselves. For FHIR `DiagnosticReport` and Health Gorilla ingested reports, `LabValue` records are attached to these tests.
    ```python
    from canvas_sdk.v1.data.lab import LabReport
    lab_report = LabReport.objects.get(id="bcd287b7-8b04-4540-a1ea-6529eb576565")
    for test in lab_report.ordered_tests:
        print(f"Ordered: {test.ontology_test_name}")
    for test in lab_report.result_tests:
        print(f"Result: {test.ontology_test_name}")
        for value in test.values.all():
            print(f"  {value.value} {value.units}")
    ```
When iterating many reports at once, the `LabReport` queryset exposes `with_result_tests_and_values()` to prefetch each report's result tests (with their values) and the report's full value list in bulk:
    ```python
    from canvas_sdk.v1.data.lab import LabReport
    reports = (
        LabReport.objects
        .filter(patient__id="patient-id")
        .with_result_tests_and_values()
    )
    ```
To query all lab reports for a particular patient, the `patient` argument can be used:
    ```python
    from logger import log
    from canvas_sdk.v1.data.lab import LabReport
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="6cbc40b408294a5f9b41f57ba1b2b487")
    lab_report = LabReport.objects.filter(patient=patient)
    ```
##  Example 
The following plugin code will run every time a new Lab Report is created and log the patient it is for, along with the values and codings from the report's results:
    ```python
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from logger import log
    from canvas_sdk.v1.data.lab import LabReport
    class MyHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.LAB_REPORT_CREATED)
        def compute(self):
            lab_report = LabReport.objects.select_related("patient").get(id=self.target)
            if lab_report.patient:
                log.info(f"{lab_report.patient.first_name} {lab_report.patient.last_name}")
            for value in lab_report.values.all():
                log.info(f"{value.value} {value.units}")
                for coding in value.codings.all():
                    log.info(coding.system)
                    log.info(coding.name)
                    log.info(coding.code)
            return []
    ```
For complete field documentation on all lab models, see the Attributes section below.
###  Working with Lab Orders and Tests 
You can also work with lab orders and their associated tests. Here's an example of querying a lab order and checking the status of its tests:
    ```python
    from canvas_sdk.v1.data.lab import LabOrder, LabTest
    # Get a lab order by ID
    lab_order = LabOrder.objects.get(id="abc123...")
    # Access all tests in the order
    for test in lab_order.tests.all():
        print(f"Test: {test.ontology_test_name}")
        print(f"Status: {test.status}")
        print(f"Code: {test.ontology_test_code}")
        # Check if results have been received
        if test.report:
            print(f"Report available with {test.report.values.count()} values")
    ```
###  Navigating Between Lab Orders and Reports 
Lab orders and lab reports are connected through the `LabTest` model. Here's how to navigate between them:
####  Getting the LabOrder from a LabReport 
    ```python
    from canvas_sdk.v1.data.lab import LabReport
    # Get a lab report
    lab_report = LabReport.objects.get(id="report-id")
    # Direct access to all orders via the reverse many-to-many relationship
    for lab_order in lab_report.laborder_set.all():
        print(f"Order ID: {lab_order.id}")
        print(f"Ordered by: {lab_order.ordering_provider.full_name if lab_order.ordering_provider else 'N/A'}")
        print(f"Date ordered: {lab_order.date_ordered}")
    # Alternatively, access the order through the tests
    for test in lab_report.tests.all():
        lab_order = test.order
        print(f"Order ID: {lab_order.id}")
        break  # Usually all tests in a report share the same order
    ```
####  Getting LabReports from a LabOrder 
    ```python
    from canvas_sdk.v1.data.lab import LabOrder
    # Get a lab order
    lab_order = LabOrder.objects.get(id="order-id")
    # Direct access to all reports via the many-to-many relationship
    for report in lab_order.reports.all():
        print(f"Report ID: {report.id}")
        print(f"Date performed: {report.date_performed}")
        print(f"Number of values: {report.values.count()}")
    # Alternatively, access reports through the tests if you need test-level details
    for test in lab_order.tests.all():
        if test.report:
            print(f"Test: {test.ontology_test_name}")
            print(f"Report ID: {test.report.id}")
    ```
###  Working with Diagnostic Reports 
A `LabReport` may be linked to one or more `DiagnosticReport` records. The `DiagnosticReport` model exposes its `id`, `status`, the `subject` (Patient), and the `lab` foreign key back to the originating `LabReport`.
####  Getting the DiagnosticReport(s) from a LabReport 
    ```python
    from canvas_sdk.v1.data.lab import LabReport
    lab_report = LabReport.objects.get(id="report-id")
    for diagnostic_report in lab_report.diagnostic_reports.all():
        print(f"DiagnosticReport ID: {diagnostic_report.id}")
        print(f"Status: {diagnostic_report.status}")
    ```
####  Following a DiagnosticReport back to its LabReport 
    ```python
    from canvas_sdk.v1.data.diagnostic_report import DiagnosticReport
    diagnostic_report = DiagnosticReport.objects.get(id="diagnostic-report-id")
    # Follow the `lab` foreign key back to the originating LabReport
    lab_report = diagnostic_report.lab
    if lab_report:
        print(f"LabReport ID: {lab_report.id}")
    ```
####  Filtering DiagnosticReports by patient 
    ```python
    from canvas_sdk.v1.data.diagnostic_report import DiagnosticReport
    diagnostic_reports = DiagnosticReport.objects.for_patient("patient-id")
    ```
####  Reconciling with FHIR 
A `DiagnosticReport`'s `id` is the same id used by the FHIR API, so you can start from a `LabReport`, grab its `DiagnosticReport`, and use the FHIR client to read the corresponding FHIR [DiagnosticReport](/api/diagnosticreport/) resource:
    ```python
    from canvas_sdk.clients.canvas_fhir import CanvasFhir
    from canvas_sdk.v1.data.lab import LabReport
    lab_report = LabReport.objects.get(id="report-id")
    diagnostic_report = lab_report.diagnostic_reports.first()
    # Declare these secrets in the CANVAS_MANIFEST.json and set the values on the
    # plugin configuration page.
    client = CanvasFhir(
        self.secrets["CANVAS_FHIR_CLIENT_ID"],
        self.secrets["CANVAS_FHIR_CLIENT_SECRET"],
    )
    # Use the DiagnosticReport's id to read the corresponding FHIR DiagnosticReport resource.
    fhir_diagnostic_report = client.read("DiagnosticReport", str(diagnostic_report.id))
    ```
###  Working with Lab Reviews 
Lab reviews track the clinical review process for lab results, including provider comments and patient communication. Here's how to work with the LabReport and LabReview relationship:
####  Accessing the Review from a LabReport 
    ```python
    from canvas_sdk.v1.data.lab import LabReport
    # Get a lab report
    lab_report = LabReport.objects.get(id="report-id")
    # Check if the report has been reviewed
    if lab_report.review:
        lab_review = lab_report.review
        print(f"Review status: {lab_review.status}")
        print(f"Internal comment: {lab_review.internal_comment}")
        print(f"Message to patient: {lab_review.message_to_patient}")
        # Access the provider who reviewed it
        if lab_review.originator:
            print(f"Reviewed by: {lab_review.originator.full_name}")
    else:
        print("Report has not been reviewed yet")
    ```
####  Accessing Reports from a LabReview 
    ```python
    from canvas_sdk.v1.data.lab import LabReview
    # Get a lab review
    lab_review = LabReview.objects.get(id="review-id")
    # Access all reports in this review batch
    for report in lab_review.reports.all():
        print(f"Report ID: {report.id}")
        print(f"Date performed: {report.date_performed}")
        print(f"Number of values: {report.values.count()}")
        # Check if this report requires signature
        if report.requires_signature:
            print("  ⚠️  Requires provider signature")
    ```
####  Finding Unreviewed Lab Reports 
    ```python
    from canvas_sdk.v1.data.lab import LabReport
    from canvas_sdk.v1.data.patient import Patient
    # Get all unreviewed lab reports for a patient
    patient = Patient.objects.get(id="patient-id")
    unreviewed_reports = LabReport.objects.filter(
        patient=patient,
        review__isnull=True,
        deleted=False
    )
    print(f"Found {unreviewed_reports.count()} unreviewed reports")
    for report in unreviewed_reports:
        print(f"Report from {report.date_performed} - {report.values.count()} values")
    ```
###  Filtering Lab Results by Abnormal Values 
A common use case is to identify abnormal lab values that may require clinical attention:
    ```python
    from canvas_sdk.v1.data.lab import LabReport, LabValue
    from canvas_sdk.v1.data.patient import Patient
    # Get all lab reports for a patient
    patient = Patient.objects.get(id="patient-id")
    lab_reports = LabReport.objects.filter(patient=patient)
    # Find all abnormal values
    for report in lab_reports:
        abnormal_values = report.values.filter(abnormal_flag__isnull=False).exclude(abnormal_flag="")
        if abnormal_values.exists():
            print(f"Report from {report.date_performed}:")
            for value in abnormal_values:
                for coding in value.codings.all():
                    print(f"  {coding.name}: {value.value} {value.units} (Flag: {value.abnormal_flag})")
    ```
###  Committed records 
The `committed` method returns `LabReport`, `LabReview`, `LabOrder`, and `LabOrderReason` records that have been committed and not entered in error:
    ```python
    from canvas_sdk.v1.data.lab import LabReport, LabReview, LabOrder, LabOrderReason
    committed_reports = LabReport.objects.committed()
    committed_reviews = LabReview.objects.committed()
    committed_orders = LabOrder.objects.committed()
    committed_order_reasons = LabOrderReason.objects.committed()
    ```
##  The document reference 
`LabReport` carries the report's values and review state, not a file. When the report is reviewed, Canvas renders it to a PDF and stores it on a [DocumentReference](/sdk/data-document-reference/#the-related-object) pointing back at the report.
To find it, resolve the [ContentType](/sdk/data-content-type/) at runtime from its stable `app_label` and `model` — never hardcode the per-environment `dbid` — and match `object_id` against the report's `dbid`:
    ```python
    from canvas_sdk.v1.data import ContentType, DocumentReference, LabReport
    report = LabReport.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    content_type = ContentType.objects.filter(app_label="api", model="labreport").first()
    document = DocumentReference.objects.filter(
        content_type=content_type, object_id=report.dbid
    ).first()
    url = document.document_url if document else None
    ```
> **Info:** `object_id` holds the related record's integer `dbid`, not its UUID `id`. A report that has not been reviewed yet has no document reference, so handle `None`. 
##  Attributes 
###  LabReport 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
review_mode | [DocumentReviewMode](/sdk/data-enumeration-types/#documentreviewmode)  
junked | Boolean  
requires_signature | Boolean  
assigned_date | DateTime  
patient | [Patient](/sdk/data-patient/#patient)  
transmission_type | TransmissionType  
for_test_only | Boolean  
external_id | String  
version | Integer  
requisition_number | String  
review | LabReview  
original_date | DateTime  
date_performed | DateTime  
custom_document_name | String  
originator | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
values | LabValue[]  
tests | LabTest[]  
ordered_tests | LabTest[]  
result_tests | LabTest[]  
remarks | LabReportRemark[]  
diagnostic_reports | DiagnosticReport[]  
laborder_set | LabOrder[]  
###  LabReportRemark 
Field Name | Type  
---|---  
dbid | Integer  
created | DateTime  
modified | DateTime  
report | LabReport  
comment | String  
###  DiagnosticReport 
The `DiagnosticReport` linked to a `LabReport`. The `id` is the DiagnosticReport id.
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
status | DiagnosticReportStatus  
subject | [Patient](/sdk/data-patient/#patient)  
lab | LabReport  
###  LabReview 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
originator | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
internal_comment | String  
message_to_patient | String  
status | String  
note | [Note](/sdk/data-note/#note)  
patient | [Patient](/sdk/data-patient/#patient)  
patient_communication_method | String  
reports | LabReport[]  
###  LabValue 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
report | LabReport  
value | String  
units | String  
abnormal_flag | String  
reference_range | String  
low_threshold | String  
high_threshold | String  
comment | String  
observation_status | String  
test | LabTest  
codings | LabValueCoding[]  
###  LabValueCoding 
Field Name | Type  
---|---  
dbid | Integer  
created | DateTime  
modified | DateTime  
value | LabValue  
code | String  
name | String  
system | String  
###  LabOrder 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
originator | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
patient | [Patient](/sdk/data-patient/#patient)  
note | [Note](/sdk/data-note/#note)  
ontology_lab_partner | String  
ordering_provider | [Staff](/sdk/data-staff/#staff)  
comment | String  
requisition_number | String  
is_patient_bill | Boolean  
date_ordered | DateTime  
fasting_status | Boolean  
specimen_collection_type | SpecimenCollectionType  
transmission_type | TransmissionType  
courtesy_copy_type | CourtesyCopyType  
courtesy_copy_number | String  
courtesy_copy_text | String  
parent_order | LabOrder  
healthgorilla_id | String  
manual_processing_status | ManualProcessingStatus  
manual_processing_comment | String  
labcorp_abn_url | URL  
reasons | LabOrderReason[]  
tests | LabTest[]  
reports | LabReport[]  
laborder_set | LabOrder[]  
###  LabOrderReason 
Field Name | Type  
---|---  
dbid | Integer  
created | DateTime  
modified | DateTime  
originator | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
order | LabOrder  
mode | LabReasonMode  
reason_conditions | LabOrderReasonCondition[]  
###  LabOrderReasonCondition 
Field Name | Type  
---|---  
dbid | Integer  
created | DateTime  
modified | DateTime  
reason | LabOrderReason  
condition | [Condition](/sdk/data-condition)  
###  LabTest 
Represents an individual test within a lab order. Each `LabTest` tracks the lifecycle of a specific test from order creation through processing and result receipt.
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
ontology_test_name | String  
ontology_test_code | String  
status | LabTestOrderStatus  
report | LabReport  
specimen_type | String  
specimen_source_code | String  
specimen_source_description | String  
specimen_source_coding_system | String  
order | LabOrder  
aoe_code | String  
procedure_class | String  
values | LabValue[]  
##  Enumeration types 
###  DiagnosticReportStatus 
Value | Label  
---|---  
`REGISTERED` | Registered  
`PARTIAL` | Partial  
`PRELIMINARY` | Preliminary  
`FINAL` | Final  
`AMENDED` | Amended  
`CORRECTED` | Corrected  
`APPENDED` | Appended  
`CANCELLED` | Cancelled  
`ENTERED_IN_ERROR` | Entered-in-error  
`UNKNOWN` | Unknown  
###  TransmissionType 
Value | Label  
---|---  
F | fax  
H | hl7  
M | manual  
###  SpecimenCollectionType 
Value | Label  
---|---  
L | on location  
P | patient service center  
O | other  
###  CourtesyCopyType 
Value | Label  
---|---  
A | account  
F | fax  
P | patient  
###  ManualProcessingStatus 
Value | Label  
---|---  
NEEDS_REVIEW | Needs Review  
IN_PROGRESS | In Progress  
PROCESSED | Processed  
FLAGGED | Flagged  
###  LabReasonMode 
Value | Label  
---|---  
MO | monitor  
IN | investigate  
SF | screen for  
UNK | unknown  
###  LabTestOrderStatus 
Value | Label  
---|---  
NE | new  
SR | staged for requisition  
SE | sending  
SF | sending failed  
PR | processing  
PF | processing failed  
RE | received  
RV | reviewed  
IN | inactive  
----- END PAGE https://docs.canvasmedical.com/sdk/data-labs/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-letter-action-event/
##  Introduction 
The `LetterActionEvent` model represents occurrences of a letter being printed or faxed within Canvas. LetterActionEvents are associated with a [Letter](/sdk/data-letter/).
##  Basic Usage 
###  Retrieve a specific letter action event 
To get a letter action event by identifier, use the `get` method on the `LetterActionEvent` model manager:
    ```python
    from canvas_sdk.v1.data.letter import LetterActionEvent
    letterActionEvent = LetterActionEvent.objects.get(id="b5a0c1d2-e3f4-5678-9abc-def012345678")
    ```
###  Find a letter action event for a specific letter 
If you have a letter object, you can access its associated letter_action_events using the `letter_action_events` attribute:
    ```python
    from canvas_sdk.v1.data.letter import Letter
    letter = Letter.objects.get(id="89992c23-c298-4118-864a-26cb3e1ae822")
    letter_action_events = letter.letter_action_events
    ```
##  Filtering 
LetterActionEvents can be filtered by any attribute that exists on the model.
###  By attribute 
Filtering for letter action events is done with the `filter` method on the `LetterActionEvent` model manager:
    ```python
    from canvas_sdk.v1.data.letter import LetterActionEvent
    # Find all successful deliveries
    delivered_letters = LetterActionEvent.objects.filter(delivered_by_fax=True)
    # Find letter action events with a specific send_fax_id
    letter_action_events = LetterActionEvent.objects.filter(send_fax_id="a1b2c3d4e5f6")
    ```
##  Attributes 
###  LetterActionEvent 
Field Name | Type | Notes  
---|---|---  
id | UUID |   
dbid | Integer |   
created | DateTime |   
modified | DateTime |   
event_type | EventType | The type of the event  
send_fax_id | String | The id of the sent fax  
received_by_fax | Boolean | The isSuccess status of the received by fax  
delivered_by_fax | Boolean | The isSuccess status of the delivered by fax  
fax_result_msg | str | The fax result message  
letter | [Letter](/sdk/data-letter/) | The letter this action event is associated with  
originator | [User](/sdk/data-canvasuser/) | The user who created the letter (nullable)  
##  Enumeration types 
###  Event Type 
Value | Label  
---|---  
PRINTED | Printed  
FAXED | Faxed
----- END PAGE https://docs.canvasmedical.com/sdk/data-letter-action-event/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-letter/
##  Introduction 
The `Letter` model represents patient correspondence letters created within Canvas. Letters are associated with a [Note](/sdk/data-note/) and contain rendered content that can be printed or sent to patients.
##  Basic Usage 
###  Retrieve a specific letter 
To get a letter by identifier, use the `get` method on the `Letter` model manager:
    ```python
    from canvas_sdk.v1.data.letter import Letter
    letter = Letter.objects.get(id="b5a0c1d2-e3f4-5678-9abc-def012345678")
    ```
###  Find a letter for a specific note 
If you have a note object, you can access its associated letter using the `letter` attribute:
    ```python
    from canvas_sdk.v1.data.note import Note
    note = Note.objects.get(id="89992c23-c298-4118-864a-26cb3e1ae822")
    letter = note.letter
    ```
###  Find all letters created by a staff member 
If you have a staff object, you can find all letters they created using the `letters` attribute:
    ```python
    from canvas_sdk.v1.data.staff import Staff
    staff = Staff.objects.get(id="a1b2c3d4e5f6")
    staff_letters = staff.letters.all()
    ```
##  Filtering 
Letters can be filtered by any attribute that exists on the model.
###  By attribute 
Filtering for letters is done with the `filter` method on the `Letter` model manager:
    ```python
    from canvas_sdk.v1.data.letter import Letter
    # Find all printed letters
    printed_letters = Letter.objects.filter(printed__isnull=False)
    # Find letters created by a specific staff member
    staff_letters = Letter.objects.filter(staff_id="a1b2c3d4e5f6")
    ```
##  The document reference 
`content` holds the letter's body, not the document that goes out. Canvas renders the letter — including anything attached to it — to a PDF and stores it on a [DocumentReference](/sdk/data-document-reference/#the-related-object) pointing back at the letter.
To read that PDF, resolve the [ContentType](/sdk/data-content-type/) at runtime from its stable `app_label` and `model` — never hardcode the per-environment `dbid` — and match `object_id` against the letter's `dbid`:
    ```python
    from canvas_sdk.v1.data import ContentType, DocumentReference, Letter
    letter = Letter.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    content_type = ContentType.objects.filter(app_label="api", model="letter").first()
    document = DocumentReference.objects.filter(
        content_type=content_type, object_id=letter.dbid
    ).first()
    url = document.document_url if document else None
    ```
> **Info:** `object_id` holds the related record's integer `dbid`, not its UUID `id`. The PDF is rendered after the letter is created rather than with it, so handle `None`. 
##  Attributes 
###  Letter 
Field Name | Type | Notes  
---|---|---  
id | UUID |   
dbid | Integer |   
created | DateTime |   
modified | DateTime |   
content | String | The rendered letter content  
printed | DateTime | When the letter was printed (null if not printed)  
note | [Note](/sdk/data-note/) | The note this letter is associated with  
staff | [Staff](/sdk/data-staff/#staff) | The staff member who created the letter (nullable)  
letter_action_events | QuerySet[LetterActionEvent] | Action events (e.g. printed, faxed) recorded for this letter
----- END PAGE https://docs.canvasmedical.com/sdk/data-letter/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-medication-history/
##  Introduction 
The `MedicationHistoryMedication` model represents historical medication data for a patient, typically imported from external sources such as health information exchanges or pharmacy systems. The `MedicationHistoryResponse` model tracks responses to medication history requests.
##  Basic usage 
To get a medication history record by identifier, use the `get` method on the `MedicationHistoryMedication` model manager:
    ```python
    from canvas_sdk.v1.data.medication_history import MedicationHistoryMedication
    medication_history = MedicationHistoryMedication.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    ```
If you have a patient object, the medication history for a patient can be accessed with the `medication_history_medications` attribute on a `Patient` object:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    medication_history = patient.medication_history_medications.all()
    ```
##  Codings 
The codings for a medication history record can be accessed with the `codings` attribute on a `MedicationHistoryMedication` object:
    ```python
    from canvas_sdk.v1.data.medication_history import MedicationHistoryMedication
    from logger import log
    medication_history = MedicationHistoryMedication.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    for coding in medication_history.codings.all():
        log.info(f"system:  {coding.system}")
        log.info(f"code:    {coding.code}")
        log.info(f"display: {coding.display}")
    ```
##  Filtering 
Medication history records can be filtered by any attribute that exists on the model.
Filtering is done with the `filter` method on the `MedicationHistoryMedication` model manager.
###  By attribute 
Specify an attribute with `filter` to filter by that attribute:
    ```python
    from canvas_sdk.v1.data.medication_history import MedicationHistoryMedication
    medications = MedicationHistoryMedication.objects.filter(dea_schedule="CII")
    ```
###  By date range 
Filter by last fill date or written date:
    ```python
    from canvas_sdk.v1.data.medication_history import MedicationHistoryMedication
    from datetime import datetime
    medications = MedicationHistoryMedication.objects.filter(
        last_fill_date__gte=datetime(2023, 1, 1)
    )
    ```
##  Attributes 
###  MedicationHistoryMedication 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
patient | [Patient](/sdk/data-patient/#patient)  
drug_description | String  
strength_value | String  
strength_form | String  
strength_unit_of_measure | String  
quantity | Float  
quantity_unit_of_measure | String  
quantity_code_list_qualifier | String  
days_supply | Integer  
last_fill_date | DateTime  
written_date | DateTime  
other_date | DateTime  
other_date_qualifier | String  
substitutions | Boolean  
refills_remaining | Integer  
diagnosis_code | String  
diagnosis_qualifier | String  
diagnosis_description | String  
secondary_diagnosis_code | String  
secondary_diagnosis_qualifier | String  
secondary_diagnosis_description | String  
dea_schedule | String  
potency_unit_code | String  
etc_path_id | Array[Integer]  
etc_path_name | Array[String]  
fill_number | Integer  
prescriber_order_number | String  
source_description | String  
source_qualifier | String  
source_payer_id | String  
source_type | String  
note | String  
sig | String  
prior_authorization_status | String  
prior_authorization | String  
pharmacy_name | String  
pharmacy_ncpdp_id | String  
pharmacy_npi | String  
prescriber_business_name | String  
prescriber_first_name | String  
prescriber_last_name | String  
prescriber_npi | String  
prescriber_dea_number | String  
codings | MedicationHistoryMedicationCoding[]  
###  MedicationHistoryMedicationCoding 
Field Name | Type  
---|---  
dbid | Integer  
system | String  
version | String  
code | String  
display | String  
user_selected | Boolean  
medication | MedicationHistoryMedication  
###  MedicationHistoryResponse 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
patient | [Patient](/sdk/data-patient/#patient)  
staff | [Staff](/sdk/data-staff/#staff)  
message_id | String  
related_to_message_id | String  
status | MedicationHistoryResponseStatus  
reason | String  
reason_code | String  
note | String  
start_date | Date  
end_date | Date  
##  Enumeration types 
###  MedicationHistoryResponseStatus 
Value | Label  
---|---  
approved | approved  
denied | denied  
----- END PAGE https://docs.canvasmedical.com/sdk/data-medication-history/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-medication-statement/
##  Introduction 
The `MedicationStatement` model represents a record of a medication statement by a patient from the past.
##  Basic usage 
To get a medication statement by identifier, use the `get` method on the `MedicationStatement` model manager:
    ```python
    from canvas_sdk.v1.data import MedicationStatement
    medication_statement = MedicationStatement.objects.get(id="61a1853f-168f-4ed3-80d2-44e5d144bcf3")
    ```
If you have a patient object, the medication statements for a patient can be accessed with the `medication_statements` attribute on a `Patient` object:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    medication_statements = patient.medication_statements.all()
    ```
You can also access the referenced medication with the `medication` attribute:
    ```python
    from canvas_sdk.v1.data import MedicationStatement
    medication_statement = MedicationStatement.objects.get(id="61a1853f-168f-4ed3-80d2-44e5d144bcf3")
    medication = medication_statement.medication
    ```
Or for a given medication, you can access all medication statements:
    ```python
    from canvas_sdk.v1.data import Medication
    medication = Medication.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    medication_statements = medication.medication_statements.all()
    ```
##  Committed records 
The `committed` method returns medication statements that have been committed and not entered in error:
    ```python
    from canvas_sdk.v1.data import MedicationStatement
    committed_medication_statements = MedicationStatement.objects.committed()
    ```
##  Attributes 
###  MedicationStatement 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
patient | [Patient](/sdk/data-patient/#patient)  
note | [Note](/sdk/data-note)  
medication | [Medication](/sdk/data-medication)  
indications | [Assessment](/sdk/data-assessment)[]  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
originator | [CanvasUser](/sdk/data-canvasuser)  
created | DateTime  
modified | DateTime  
start_date_original_input | String  
start_date | Date  
end_date_original_input | String  
end_date | Date  
dose_quantity | Number  
dose_form | String  
dose_route | String  
dose_frequency | Number  
dose_frequency_interval | String  
sig_original_input | String  
----- END PAGE https://docs.canvasmedical.com/sdk/data-medication-statement/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-medication/
##  Introduction 
The `Medication` model represents a record of a medication that is being consumed by a patient, either now, in the past, or in the future. `Medication` records can represent both prescriptions and medication statements for a patient.
##  Basic usage 
To get a medication by identifier, use the `get` method on the `Medication` model manager:
    ```python
    from canvas_sdk.v1.data.medication import Medication
    medication = Medication.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    ```
If you have a patient object, the medications for a patient can be accessed with the `medications` attribute on a `Patient` object:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    medications = patient.medications.all()
    ```
If you have a patient ID, you can get the medications for the patient with the `for_patient` method on the `Medication` model manager:
    ```python
    from canvas_sdk.v1.data.medication import Medication
    patient_id = "1eed3ea2a8d546a1b681a2a45de1d790"
    medication = Medication.objects.for_patient(patient_id)
    ```
#  Codings 
The codings for a medication can be accessed with the `codings` attribute on an `Medication` object:
    ```python
    from canvas_sdk.v1.data.medication import Medication
    from logger import log
    medication = Medication.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    for coding in medication.codings.all():
        log.info(f"system:  {coding.system}")
        log.info(f"code:    {coding.code}")
        log.info(f"display: {coding.display}")
    ```
##  Filtering 
Medications can be filtered by any attribute that exists on the model.
Filtering for medications is done with the `filter` method on the `Medication` model manager.
###  By attribute 
Specify an attribute with `filter` to filter by that attribute:
    ```python
    from canvas_sdk.v1.data.medication import Medication
    medications = Medication.objects.filter(status="active")
    ```
###  By ValueSet 
See [Value Sets](/sdk/data-value-sets/) for the library of built-in value sets and how to create your own.
Filtering by ValueSet works a little differently. The `find` method on the model manager is used to perform `ValueSet` filtering:
    ```python
    from canvas_sdk.v1.data.medication import Medication
    from canvas_sdk.value_set.v2022.medication import AdhdMedications
    medications = Medication.objects.find(AdhdMedications)
    ```
##  Attributes 
###  Medication 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
patient | [Patient](/sdk/data-patient/#patient)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
status | String  
start_date | Date  
end_date | Date  
quantity_qualifier_description | String  
clinical_quantity_description | String  
potency_unit_code | String  
national_drug_code | String  
erx_quantity | String  
codings | MedicationCoding[]  
medication_statements | [MedicationStatement](/sdk/data-medication-statement)[]  
change_medications | [ChangeMedication](/sdk/data-change-medication)[]  
stopmedicationevent_set | [StopMedicationEvent](/sdk/data-stop-medication-event)[]  
prescriptions | [Prescription](/sdk/data-prescription)[]  
previous_medications | [Prescription](/sdk/data-prescription)[]  
prescription_change_responses | [PrescriptionChangeResponse](/sdk/data-prescription-change-response/#prescriptionchangeresponse)[]  
###  MedicationCoding 
Field Name | Type  
---|---  
dbid | Integer  
system | String  
version | String  
code | String  
display | String  
user_selected | Boolean  
medication | Medication  
----- END PAGE https://docs.canvasmedical.com/sdk/data-medication/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-message/
#  Message Models 
The Canvas SDK defines messaging-related data models for sending, receiving, and tracking messages.
##  TransmissionChannel 
A `TextChoices` enum representing the available channels for transmitting messages.
Member | Value | Description  
---|---|---  
`MANUAL` | `manual` | Manual  
`TEXT_MESSAGE` | `sms` | Text Message  
`EMAIL` | `email` | Email  
`NOOP` | `noop` | No-op  
##  Message 
Represents an individual message record.
###  Fields 
Name | Type | Description  
---|---|---  
`id` | `UUID` | Unique identifier for the message.  
`dbid` | `Integer` | Database primary key.  
`created` | `DateTime` | Timestamp when the message was created.  
`modified` | `DateTime` | Timestamp when the message was last modified.  
`content` | `Text` | The body text of the message.  
`sender` | [CanvasUser](/sdk/data-canvasuser) | The user who sent the message. May be null.  
`recipient` | [CanvasUser](/sdk/data-canvasuser) | The user who received the message. May be null.  
`note` | [Note](/sdk/data-note) | Associated note (if any) for contextual linkage. May be null.  
`read` | `DateTime` | Timestamp when the recipient read the message. Null if unread.  
`transmissions` | QuerySet[MessageTransmission] | The delivery transmissions associated with this message.  
`message` | QuerySet[MessageAttachment] | The file attachments associated with this message.  
##  MessageAttachment 
Represents a file attachment linked to a message.
###  Fields 
Name | Type | Description  
---|---|---  
`id` | `UUID` | Unique identifier for the attachment.  
`dbid` | `Integer` | Database primary key.  
`file` | `Text` | Storage path or identifier for the file.  
`content_type` | `String` | MIME type of the attachment.  
`message` | Message | The parent message to which this belongs.  
`file_url` | String (property) | Presigned S3 URL for accessing the file.  
##  MessageTransmission 
Tracks delivery attempts and status for a message.
###  Fields 
Name | Type | Description  
---|---|---  
`id` | `UUID` | Unique identifier for the transmission record.  
`dbid` | `Integer` | Database primary key.  
`created` | `DateTime` | Timestamp when the transmission was created.  
`modified` | `DateTime` | Timestamp when the transmission was last modified.  
`message` | Message | The message associated with this transmission.  
`delivered` | `Boolean` | Whether delivery was successful.  
`failed` | `Boolean` | Whether delivery failed.  
`contact_point_system` | TransmissionChannel | The channel used for delivery.  
`contact_point_value` | `String` | The destination address or identifier (e.g., phone, email).  
`comment` | `Text` | Optional comments or error details.  
`delivered_by` | [Staff](/sdk/data-staff/#staff) | The staff member who processed the delivery. May be null.
----- END PAGE https://docs.canvasmedical.com/sdk/data-message/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-note/
##  Introduction 
The `Note` model represents clinical notes that appear on a patient's chart. A `Note` can contain multiple [commands](/sdk/data-command).
##  Basic usage 
###  Retrieve a specific note 
To get a note by identifier, use the `get` method on the `Note` model manager:
    ```python
    from canvas_sdk.v1.data.note import Note
    note = Note.objects.get(id="89992c23-c298-4118-864a-26cb3e1ae822")
    ```
###  Find all notes for a patient 
If you have a patient object, the notes for a patient can be found using the `notes` attribute on the `Patient` instance:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="fd2ecd87c26044a6a755287f296dd17f")
    patient_notes = patient.notes.all()
    ```
###  Retrieve the content of commands in a note 
If you have a note object, the [commands](/sdk/data-command) for that note can be found using the `commands` attribute on the `Note` instance:
    ```python
    from canvas_sdk.v1.data.note import Note
    note = Note.objects.get(id="89992c23-c298-4118-864a-26cb3e1ae822")
    note_commands = note.commands.all()
    ```
You can also filter commands by their state or other attributes:
    ```python
    from canvas_sdk.v1.data.note import Note
    note = Note.objects.get(id="89992c23-c298-4118-864a-26cb3e1ae822")
    # Get only committed commands
    committed_commands = note.commands.filter(state="committed")
    # Get commands by schema_key (e.g., prescriptions)
    prescriptions = note.commands.filter(schema_key="prescribe")
    ```
To access the content of a command, use the `data` attribute which contains a JSON object with the command's data:
    ```python
    import json
    from canvas_sdk.v1.data.note import Note
    note = Note.objects.get(id="89992c23-c298-4118-864a-26cb3e1ae822")
    for command in note.commands.all():
        # Get the command type
        command_type = command.schema_key
        # Get the command data as a dictionary
        command_data = command.data
        # Pretty print the command data
        print(f"Command Type: {command_type}")
        print(json.dumps(command_data, indent=2))
    ```
For more information about command types and their data structure, see the [Command](/sdk/data-command/) documentation.
###  Retrieve educational materials for a note 
Educational material shared through the Educational Material command is recorded on the note. If you have a note object, those records can be found using the `education_material` reverse relation:
    ```python
    from canvas_sdk.v1.data.note import Note
    note = Note.objects.get(id="89992c23-c298-4118-864a-26cb3e1ae822")
    educational_materials = note.education_material.all()
    ```
###  Understanding the note body structure 
The `body` of a note is a JSON array that represents the structure and layout of the note. It intermixes text content with references to commands:
    ```python
    import json
    from canvas_sdk.v1.data.note import Note
    note = Note.objects.get(id="89992c23-c298-4118-864a-26cb3e1ae822")
    # The body is an array of objects
    print(json.dumps(note.body, indent=2))
    ```
The body array contains objects of two types:
  1. **Text objects** : Represent free-form text content 
         ```json
         {"type": "text", "value": "Patient reports feeling better"}
         ```
  2. **Command objects** : Reference commands with their metadata 
         ```json
         {
           "type": "command",
           "value": "reasonForVisit",
           "data": {
             "id": 1095,
             "command_uuid": "691123c4-6c7d-415b-880b-2beefab9f64a"
           }
         }
         ```
####  Querying on the body 
`body` is computed on each access rather than stored in a column, because Canvas assembles it from more than one column. That does not change the value you read, but it does limit which query operations can name it:
Operation | Supported | Notes  
---|---|---  
`Note.objects.filter(body=...)` | Yes | Also `exclude()` and `get()`, and lookups nested inside a `Q` object  
`Note.objects.only("body")` | Yes | Loads every column the property reads, so building a body costs no further queries  
`Note.objects.defer("body")` | Yes | Defers all of them  
`Note.objects.values("body")`, `values_list("body")` | No | Raises a `FieldError` telling you to use `only("body")`. No single column holds the value to return  
`Note.objects.order_by("body")` | No | Raises a `FieldError`  
`body` named through a relation | No | For example `Appointment.objects.defer("note__body")` or `filter(note__body=...)`. Query `Note` itself instead  
> **Warning:** Naming `body` through a relation stopped working in the [September 8, 2026 release](/release-notes/1-348-0/). A queryset on another model that defers or filters `note__body` now raises an error. If you were deferring it to keep a large body out of a joined scan, query the notes you need separately with `Note.objects.defer("body")`. 
So read the body from a note you already have, or filter notes by it, rather than trying to select it as a value:
    ```python
    from canvas_sdk.v1.data.note import Note
    # Load only the columns the body needs.
    notes = Note.objects.only("body").filter(patient__id="b80b1cdc2e6a4aca90ccebc02e683f35")
    for note in notes:
        print(note.body)
    ```
The `command_uuid` in a command object corresponds to the `id` field of the [Command](/sdk/data-command/) model, allowing you to retrieve the full command data:
    ```python
    from canvas_sdk.v1.data.note import Note
    from canvas_sdk.v1.data.command import Command
    note = Note.objects.get(id="89992c23-c298-4118-864a-26cb3e1ae822")
    # Find all command references in the note body
    for item in note.body:
        if item.get("type") == "command":
            command_uuid = item["data"]["command_uuid"]
            command = Command.objects.get(id=command_uuid)
            print(f"Command type: {command.schema_key}")
            print(f"Command data: {command.data}")
    ```
###  Retrieve the audit history for a note 
The audit history for a note can be found using the [`NoteStateChangeEvent`](/sdk/data-note/#notestatechangeevent) model. You can access this model directly or through the `state_history` relation on the note object.
    ```python
    from canvas_sdk.v1.data.note import Note, NoteStateChangeEvent
    note = Note.objects.first()
    # Use the state_history relation
    option_1 = note.state_history.all()
    # Use the note object to filter the QuerySet
    option_2 = NoteStateChangeEvent.objects.filter(note=note)
    # Use the note's UUID to filter the QuerySet, which joins to the note table
    # where the note's dbid column is equal to the note_id column of the note
    # state change event and the note's id column is equal to the note's UUID.
    option_3 = NoteStateChangeEvent.objects.filter(note__id=note.id)
    # Use the note's auto-increment database id to filter the QuerySet by the
    # foreign key column without joining to the notes table.
    option_4 = NoteStateChangeEvent.objects.filter(note_id=note.dbid)
    ```
In the above code sample, options 1, 2, and 4 produce identical SQL queries.
###  Determine if a note is locked 
To see if a note is presently locked, you can use the [`CurrentNoteStateEvent`](/sdk/data-note/#currentnotestateevent) model to check if the current note state is 'Locked'. (See: [NoteState](/sdk/data-note/#notestates) for an explanation of the different note states you might encounter)
    ```python
    from canvas_sdk.v1.data.note import Note, CurrentNoteStateEvent, NoteStates
    note = Note.objects.first()
    # You can retrieve the CurrentNoteStateEvent record for the note and check its
    # state attribute.
    if CurrentNoteStateEvent.objects.get(note=note).state == NoteStates.LOCKED:
        # This note is locked!
        pass
    # You can skip retrieving the record by just checking if a
    # CurrentNoteStateEvent record exists for that note with the state 'Locked'.
    if CurrentNoteStateEvent.objects.filter(note=note, state=NoteStates.LOCKED).exists():
        # This note is locked!
        pass
    ```
###  Retrieve the PDF of a locked note 
Locking a note captures it as a PDF showing the note at the moment of the lock. The file is stored on a [DocumentReference](/sdk/data-document-reference/#the-related-object) pointing back at the [NoteStateChangeEvent](/sdk/data-note/#notestatechangeevent) that recorded the lock, so you get there through the note's state history rather than from the note itself.
Resolve the [ContentType](/sdk/data-content-type/) at runtime from its stable `app_label` and `model` — never hardcode the per-environment `dbid` — and match `object_id` against the lock event's `dbid`:
    ```python
    from canvas_sdk.v1.data import ContentType, DocumentReference, DocumentReferenceStatus
    from canvas_sdk.v1.data.note import Note, NoteStates
    note = Note.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    lock_events = note.state_history.filter(state=NoteStates.LOCKED)
    content_type = ContentType.objects.filter(
        app_label="api", model="notestatechangeevent"
    ).first()
    document = DocumentReference.objects.filter(
        content_type=content_type,
        object_id__in=[event.dbid for event in lock_events],
        status=DocumentReferenceStatus.CURRENT,
    ).first()
    url = document.document_url if document else None
    ```
> **Info:** A note can be locked more than once. Each lock captures its own PDF, and Canvas supersedes the earlier ones — so filter on `CURRENT` for the version that is in force, or drop the status filter to see every captured version. Only encounter, inpatient, and review note types are captured this way; other note types have no PDF. 
###  Find all open notes 
You can find all open notes by retrieving the note records with a current state which indicates it can be edited. (See list below)
    ```python
    from canvas_sdk.v1.data.note import Note, CurrentNoteStateEvent, NoteStates
    open_note_states = [
        NoteStates.NEW,
        NoteStates.PUSHED,
        NoteStates.CONVERTED,
        NoteStates.UNLOCKED,
        NoteStates.RESTORED,
        NoteStates.UNDELETED,
    ]
    # This will execute one query per CurrentNoteStateEvent object returned
    open_notes_via_list_comprehension = [event.note for event in CurrentNoteStateEvent.objects.filter(state__in=open_note_states)]
    # This will always execute two queries: one to find the note ids of open
    # notes, and a second query to fetch the note records by the ids returned in the
    # first query
    open_note_ids = CurrentNoteStateEvent.objects.filter(state__in=open_note_states).values_list('note_id', flat=True)
    open_notes_via_multiple_queries = Note.objects.filter(dbid__in=open_note_ids)
    ```
###  Get the current state of a given note 
To get a note's current state, retrieve its [`CurrentNoteStateEvent`](/sdk/data-note/#currentnotestateevent) and check the `state` attribute. If you are trying to assess if the current note state represents that note as being editable, you can call the `editable()` method on the `CurrentNoteStateEvent` object.
    ```python
    from canvas_sdk.v1.data.note import Note, CurrentNoteStateEvent
    note = Note.objects.first()
    current_note_state = CurrentNoteStateEvent.objects.get(note=note).state
    is_editable = current_note_state.editable()
    ```
###  Get the current claim of a given note 
You can retrieve the current claim using the method `get_claim()` presented in the Note object.
    ```python
    from canvas_sdk.v1.data.note import Note
    note = Note.objects.first()
    claim = note.get_claim()
    ```
###  Get the NoteType of a given note 
To get the note type for a specific note, use the `note_type_version` attribute which provides access to the related `NoteType` object:
    ```python
    from canvas_sdk.v1.data.note import Note
    note = Note.objects.get(id="89992c23-c298-4118-864a-26cb3e1ae822")
    # Get the note type name (e.g., "Office Visit")
    note_type_name = note.note_type_version.name
    # Access other note type attributes
    note_type_display = note.note_type_version.display
    note_type_code = note.note_type_version.code
    note_type_system = note.note_type_version.system
    ```
##  Filtering 
###  By attribute 
Notes can also be filtered by attribute. For example, to get all notes for a patient where the `datetime_of_service` is after a certain date, the following code can be used:
    ```python
    import arrow
    from canvas_sdk.v1.data.note import Note
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="fd2ecd87c26044a6a755287f296dd17f")
    recent_notes = Note.objects.filter(
        patient=patient,
        datetime_of_service__gte=arrow.now().shift(weeks=-3).datetime
    )
    ```
The `NoteType` model can also be used to find notes by type.
    ```python
    from canvas_sdk.v1.data.note import Note
    from canvas_sdk.v1.data.note import NoteType
    from canvas_sdk.v1.data.patient import Patient
    note_type = NoteType.objects.get(name="Office visit")
    patient = Patient.objects.get(id="fd2ecd87c26044a6a755287f296dd17f")
    patient_office_visits = Note.objects.filter(patient=patient, note_type_version=note_type)
    ```
##  Attributes 
###  Note 
Field Name | Type | Notes  
---|---|---  
id | UUID |   
dbid | Integer |   
created | DateTime |   
modified | DateTime |   
patient | [Patient](/sdk/data-patient/#patient) |   
note_type_version | NoteType |   
title | String |   
body | JSON (computed) | Array of objects representing the note structure. Each object has a `type` (either `"text"` or `"command"`) and a `value`. Command objects also include a `data` field with `id` and `command_uuid`. See Querying on the body.  
originator | [CanvasUser](/sdk/data-canvasuser) |   
provider | [Staff](/sdk/data-staff/#staff) |   
supervising_provider | [Staff](/sdk/data-staff/#staff) | The note's supervising provider, if one has been set  
last_modified_by_staff | [Staff](/sdk/data-staff/#staff) | The staff member who last modified the note  
checksum | String |   
billing_note | String |   
related_data | JSON | Can contain one key, `roomNumber`, if the Note is an inpatient stay.  
datetime_of_service | DateTime |   
place_of_service | String |   
encounter | [Encounter](/sdk/data-encounter) |   
location | [PracticeLocation](/sdk/data-practicelocation/#practicelocation) | The practice location associated with the note  
commands | QuerySet[[Command](/sdk/data-command)] | All commands associated with this note  
note_tasks | QuerySet[[NoteTask](/sdk/data-task)] | All tasks associated with this note  
metadata | QuerySet[NoteMetadata] | All metadata key-value pairs associated with this note  
lab_reviews | QuerySet[[LabReview](/sdk/data-labs/#labreview)] | All lab reviews associated with this note  
imaging_reviews | QuerySet[[ImagingReview](/sdk/data-imaging/#imagingreview)] | All imaging reviews associated with this note  
referral_reviews | QuerySet[[ReferralReview](/sdk/data-referral/#referralreview)] | All referral reviews associated with this note  
chart_section_reviews | QuerySet[[ChartSectionReview](/sdk/data-chart-section-review/#chartsectionreview)] | All chart section reviews associated with this note  
visual_exam_findings | QuerySet[[VisualExamFinding](/sdk/data-visual-exam-finding/#visualexamfinding)] | All visual exam findings associated with this note  
state_history | QuerySet[NoteStateChangeEvent] | The note's state-change audit history  
current_state | CurrentNoteStateEvent | The note's current state event  
assessments | QuerySet[[Assessment](/sdk/data-assessment/#assessment)] | All assessments associated with this note  
goals | QuerySet[[Goal](/sdk/data-goal/#goal)] | All goals associated with this note  
updategoals | QuerySet[[UpdateGoal](/sdk/data-goal/#updategoal)] | All goal updates and closures recorded on this note  
instructions | QuerySet[[Instruction](/sdk/data-instruction/#instruction)] | All instructions associated with this note  
immunizations | QuerySet[[Immunization](/sdk/data-immunization/#immunization)] | All immunizations associated with this note  
claims | QuerySet[[Claim](/sdk/data-claim/#claim)] | All claims associated with this note (see the `get_claim()` method)  
letter | [Letter](/sdk/data-letter/#letter) | The letter associated with this note, if any  
referral_set | QuerySet[[Referral](/sdk/data-referral/#referral)] | All referrals associated with this note  
laborder_set | QuerySet[[LabOrder](/sdk/data-labs/#laborder)] | All lab orders associated with this note  
appointment_set | QuerySet[[Appointment](/sdk/data-appointment/#appointment)] | All appointments associated with this note  
education_material | QuerySet[[EducationalMaterial](/sdk/data-educational-material/#educationalmaterial)] | All educational materials recorded on this note  
procedures | QuerySet[[Procedure](/sdk/data-procedure/#procedure)] | All procedures recorded on this note  
family_histories | QuerySet[[FamilyHistory](/sdk/data-family-history/#familyhistory)] | All family history records recorded on this note  
plans | QuerySet[[Plan](/sdk/data-plan/#plan)] | All plans recorded on this note  
follow_ups | QuerySet[[FollowUp](/sdk/data-follow-up/#followup)] | All follow-ups recorded on this note  
reasons_for_visit | QuerySet[[ReasonForVisit](/sdk/data-reason-for-visit/#reasonforvisit)] | All reasons for visit recorded on this note  
removed_allergies | QuerySet[[RemoveAllergyEvent](/sdk/data-remove-allergy-event/#removeallergyevent)] | All allergies removed on this note  
resolved_conditions | QuerySet[[ResolveConditionEvent](/sdk/data-resolve-condition-event/#resolveconditionevent)] | All conditions resolved on this note  
histories_of_present_illness | QuerySet[[HistoryOfPresentIllness](/sdk/data-history-present-illness/#historyofpresentillness)] | All histories of present illness recorded on this note  
vital_sign_readings | QuerySet[[VitalSignReading](/sdk/data-vital-sign-reading/#vitalsignreading)] | All vital sign readings recorded on this note  
cancel_prescriptions | QuerySet[[CancelPrescription](/sdk/data-cancel-prescription/#cancelprescription)] | All prescription cancellations recorded on this note  
prescription_change_requests | QuerySet[[PrescriptionChangeRequest](/sdk/data-prescription-change-request/#prescriptionchangerequest)] | All pharmacy change requests recorded on this note  
prescription_change_responses | QuerySet[[PrescriptionChangeResponse](/sdk/data-prescription-change-response/#prescriptionchangeresponse)] | All responses to change requests recorded on this note  
###  NoteType 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
system | String  
version | String  
code | String  
display | String  
user_selected | Boolean  
name | String  
icon | String  
category | NoteTypeCategories  
rank | Integer  
is_default_appointment_type | Boolean  
is_scheduleable | Boolean  
is_telehealth | Boolean  
is_billable | Boolean  
defer_place_of_service_to_practice_location | Boolean  
available_places_of_service | Array[PracticeLocationPOS]  
default_place_of_service | PracticeLocationPOS  
is_system_managed | Boolean  
is_visible | Boolean  
is_active | Boolean  
unique_identifier | UUID  
deprecated_at | DateTime  
is_patient_required | Boolean  
allow_custom_title | Boolean  
is_scheduleable_via_patient_portal | Boolean  
online_duration | Integer  
is_sig_required | Boolean  
notes | QuerySet[Note]  
appointments | QuerySet[[Appointment](/sdk/data-appointment/#appointment)]  
###  NoteMetadata 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
note | Note  
key | String  
value | String  
    ```python
    from canvas_sdk.v1.data.note import Note
    from logger import log
    note_id = "89992c23-c298-4118-864a-26cb3e1ae822"
    note = Note.objects.get(id=note_id)
    note_metadata = note.metadata.all()
    for metadata in note_metadata:
       log.info(f"Note metadata: {metadata.key}, {metadata.value}")
    ```
###  NoteStateChangeEvent 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
note | [Note](/sdk/data-note/)  
originator | [CanvasUser](/sdk/data-canvasuser)  
state | [NoteState](/sdk/data-note/#notestates)  
note_state_document | String  
note_state_html | String  
###  CurrentNoteStateEvent 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
state | [NoteState](/sdk/data-note/#notestates)  
note | [Note](/sdk/data-note/)  
##  Enumeration types 
###  NoteStates 
Value | Description | Notes  
---|---|---  
NEW | Created |   
PSH | Pushed the charges for |   
LKD | Locked |   
ULK | Unlocked |   
DLT | Deleted |   
RLK | Relocked |   
RST | Restored |   
RCL | Recalled |   
UND | Undeleted |   
DSC | Discharged |   
SGN | Signed | Used when the note type's `is_sig_required` is True  
SCH | Scheduling | Used in appointment notes  
BKD | Booked | Used in appointment notes  
CVD | Converted | Used in appointment notes  
CLD | Canceled | Used in appointment notes  
NSW | No show | Used in appointment notes  
RVT | Reverted | Used in appointment notes  
CNF | Confirmed | Used for CCDA import notes  
###  NoteTypeCategories 
Value | Description  
---|---  
message | Message  
letter | Letter  
inpatient | Inpatient Visit Note  
review | Chart Review Note  
encounter | Encounter Note  
appointment | Appointment Note  
task | Task  
data | Data  
ccda | C-CDA  
schedule_event | Schedule Event  
###  PracticeLocationPOS 
Value | Description  
---|---  
01 | Pharmacy  
02 | Telehealth  
03 | Education Facility  
04 | Homeless Shelter  
09 | Prison  
10 | Telehealth in Patient's Home  
11 | Office  
12 | Home  
13 | Asssisted Living Facility  
14 | Group Home  
15 | Mobile Unit  
17 | Walk-In Retail Health Clinic  
19 | Off-Campus Outpatient Hospital  
20 | Urgent Care Facility  
21 | Inpatient Hospital  
22 | On-Campus Outpatient Hospital  
23 | Emergency Room Hospital  
24 | Ambulatory Surgery Center  
25 | Birthing Center  
26 | Military Treatment Facility  
27 | Outreach Site / Street  
31 | Skilled Nursing Facility  
32 | Nursing Facility  
33 | Custodial Care Facility  
34 | Hospice  
41 | Ambulance Land  
42 | Ambulance Air or Water  
49 | Independent Clinic  
50 | Federally Qualified Health Center  
51 | Inpatient Psychiatric Facility  
52 | Inpatient Psychiatric Facility - Partial Hospitalization  
53 | Community Mental Health Center  
54 | Intermediate Care Facility for Mentally Retarded  
55 | Residential Substance Abuse Treatment Facility  
56 | Psychiatric Residential Treatment Center  
57 | Non-Residential Substance Abuse Treatment Facility  
60 | Mass Immunization Center  
61 | Inpatient Rehabilitation Facility  
62 | Outpatient Rehabilitation Facility  
65 | End-Stage Renal Disease Treatment Facility  
71 | State or Local Public Health Clinic  
72 | Rural Health Clinic  
81 | Independent Laboratory  
99 | Other Place of Service
----- END PAGE https://docs.canvasmedical.com/sdk/data-note/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-observation/
##  Introduction 
The `Observation` model represents measurements or assertions made about a patient, such as vital signs, lab results, or other clinical findings.
##  Basic usage 
To get an observation by identifier, use the `get` method on the `Observation` model manager:
    ```python
    from canvas_sdk.v1.data.observation import Observation
    observation = Observation.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    ```
If you have a patient object, the observations for a patient can be accessed with the `observations` attribute on a `Patient` object:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    observations = patient.observations.all()
    ```
If you have a patient ID, you can get the observations for the patient with the `for_patient` method on the `Observation` model manager:
    ```python
    from canvas_sdk.v1.data.observation import Observation
    patient_id = "1eed3ea2a8d546a1b681a2a45de1d790"
    observations = Observation.objects.for_patient(patient_id)
    ```
##  Codings 
The codings for an observation can be accessed with the `codings` attribute on an `Observation` object:
    ```python
    from canvas_sdk.v1.data.observation import Observation
    from logger import log
    observation = Observation.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    for coding in observation.codings.all():
        log.info(f"system:  {coding.system}")
        log.info(f"code:    {coding.code}")
        log.info(f"display: {coding.display}")
    ```
##  Components 
The components for an observation can be accessed with the `components` attribute on an `Observation` object:
    ```python
    from canvas_sdk.v1.data.observation import Observation
    from logger import log
    observation = Observation.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    for component in observation.components.all():
        log.info(f"name: {component.name}")
        log.info(f"value: {component.value_quantity}")
        log.info(f"unit: {component.value_quantity_unit}")
    ```
###  Component codings 
Component codings can be accessed similarly to codings on the observation, by using the `codings` attribute on an `ObservationComponent` object.
##  Filtering 
Observations can be filtered by any attribute that exists on the model.
###  By attribute 
Specify an attribute with `filter` to filter by that attribute:
    ```python
    from canvas_sdk.v1.data.observation import Observation
    observations = Observation.objects.filter(effective_datetime__gte="2024-11-20")
    ```
###  By ValueSet 
See [Value Sets](/sdk/data-value-sets/) for the library of built-in value sets and how to create your own.
Filtering by ValueSet works a little differently. The `find` method on the model manager is used to perform `ValueSet` filtering:
    ```python
    from canvas_sdk.v1.data.observation import Observation
    from canvas_sdk.value_set.v2022.physical_exam import Weight
    observations = Observation.objects.find(Weight)
    ```
##  Attributes 
###  Observation 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
originator | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
patient | [Patient](/sdk/data-patient/#patient)  
is_member_of | Observation  
category | String (comma-separated list of categories  
units | String  
value | String  
note_id | Integer  
name | String  
effective_datetime | DateTime  
codings | ObservationCoding[]  
members | Observation[]  
components | ObservationComponent[]  
value_codings | ObservationValueCoding[]  
###  ObservationCoding 
Field Name | Type  
---|---  
dbid | Integer  
system | String  
version | String  
code | String  
display | String  
user_selected | Boolean  
observation | Observation  
###  ObservationComponent 
Field Name | Type  
---|---  
dbid | Integer  
created | DateTime  
modified | DateTime  
observation | Observation  
value_quantity | String  
value_quantity_unit | String  
name | String  
codings | ObservationComponentCoding[]  
###  ObservationComponentCoding 
Field Name | Type  
---|---  
dbid | Integer  
system | String  
version | String  
code | String  
display | String  
user_selected | Boolean  
observation_component | ObservationComponent  
###  ObservationValueCoding 
Field Name | Type  
---|---  
dbid | Integer  
system | String  
version | String  
code | String  
display | String  
user_selected | Boolean  
observation | Observation  
----- END PAGE https://docs.canvasmedical.com/sdk/data-observation/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-organization/
##  Introduction 
The `Organization` model represents the overall Organization in a Canvas EMR instance. An `Organization` can have multiple related [Practice Locations](/sdk/data-practicelocation).
##  Basic usage 
Canvas instances can contain only a single `Organization` entry. To retrieve the `Organization` entry, you can either query by the organization's name:
    ```python
    from canvas_sdk.v1.data.organization import Organization
    organization = Organization.objects.get(full_name="Medical Organization")
    ```
Or since there will only be one `Organization` in an instance, it can also be fetched by using the `first` method:
    ```python
    from canvas_sdk.v1.data.organization import Organization
    organization = Organization.objects.first()
    ```
##  Attributes 
###  Organization 
Field Name | Type  
---|---  
dbid | Integer  
created | DateTime  
modified | DateTime  
full_name | String  
short_name | String  
subdomain | String  
logo_url | String  
background_image_url | String  
background_gradient | String  
active | Boolean  
tax_id | String  
tax_id_type | [TaxIDType](/sdk/data-enumeration-types/#taxidtype)  
group_npi_number | String  
group_taxonomy_number | String  
include_zz_qualifier | Boolean  
main_location | [PracticeLocation](/sdk/data-practicelocation/#practicelocation)  
practice_locations | QuerySet[[PracticeLocation](/sdk/data-practicelocation/#practicelocation)]  
addresses | QuerySet[OrganizationAddress]  
telecom | QuerySet[OrganizationContactPoint]  
business_lines | QuerySet[[BusinessLine](/sdk/data-business-line/#businessline)]  
##  OrganizationAddress 
The `OrganizationAddress` model represents a physical or mailing address associated with an Organization. Multiple addresses can be linked to a single Organization, each with its own type and details.
###  Attributes 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
organization | Organization  
use | [AddressUseWithBilling](/sdk/data-enumeration-types/#addressusewithbilling)  
type | [AddressType](/sdk/data-enumeration-types/#addresstype)  
longitude | Float  
latitude | Float  
start | Date  
end | Date  
country | String  
state | [AddressState](/sdk/data-enumeration-types/#addressstate)  
address_search_index | String  
line1 | String  
line2 | String  
city | String  
district | String  
state_code | String  
postal_code | String  
##  OrganizationContactPoint 
The `OrganizationContactPoint` model represents a contact method (such as phone, email, or fax) for an Organization. Multiple contact points can be associated with a single Organization, each with its own type, use, and status.
###  Attributes 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
organization | Organization  
system | [ContactPointSystem](/sdk/data-enumeration-types/#contactpointsystem)  
value | String  
use | [ContactPointUse](/sdk/data-enumeration-types/#contactpointuse)  
use_notes | String  
rank | Integer  
state | [ContactPointState](/sdk/data-enumeration-types/#contactpointstate)  
----- END PAGE https://docs.canvasmedical.com/sdk/data-organization/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-organizational-entity/
##  Introduction 
The `OrganizationalEntity` model represents an external entity that Canvas references through a generic relation — for example, the [ServiceProvider](/sdk/data-serviceprovider/#service-provider) backing a patient's external care team member. Its `type` indicates which kind of entity it points at, and the `content_type` and `object_id` fields identify the specific record.
The most common use is reaching the external members of a patient's care team. A [CareTeamMembership](/sdk/data-care-team/#careteammembership) with no `staff` is an external member, and its `organizational_entity` links to the `OrganizationalEntity` describing the external provider.
##  Basic usage 
When an `OrganizationalEntity` has a `type` of `Service Provider`, its `service_provider` property resolves to the linked [ServiceProvider](/sdk/data-serviceprovider/#service-provider), giving you access to the provider's contact details — such as `business_fax` — without leaving the plugin:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="a74592ae8a6c4d0ebe0799d3fb3713d1")
    external_member = patient.care_team_memberships.filter(staff__isnull=True).first()
    entity = external_member.organizational_entity
    if entity and entity.service_provider:
        print(entity.service_provider.business_fax)
    ```
For entities of any other `type`, the `service_provider` property returns `None`.
##  Attributes 
###  OrganizationalEntity 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
content_type | [ContentType](/sdk/data-content-type/#contenttype)  
object_id | Integer  
name | String  
active | Boolean  
type | OrganizationalEntityType  
##  Properties 
Name | Type | Description  
---|---|---  
service_provider | [ServiceProvider](/sdk/data-serviceprovider/#service-provider) | `None` | The linked `ServiceProvider` when `type` is `Service Provider`; otherwise `None`.  
##  Enumeration types 
###  OrganizationalEntityType 
Value | Label  
---|---  
Transactor | Transactor  
Business Entity | Business Entity  
Vendor | Vendor  
Service Provider | Service Provider
----- END PAGE https://docs.canvasmedical.com/sdk/data-organizational-entity/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-patient-administrative-document/
#  PatientAdministrativeDocument 
The `PatientAdministrativeDocument` model represents patient-facing administrative documents: prior authorizations, advance directives and beneficiary notices, signed consent forms and agreements, insurance and prescription cards, driver's licenses, intake forms, releases of information, powers of attorney, and workers' compensation attachments. Each carries a document file and an optional `DocumentCoding`.
A signed consent form is one of these records: `patient_consents` lists the [PatientConsent](/sdk/data-patient-consent/#signed-consent-documents) records it was signed for. The blank template the patient was sent lives on the consent itself, not here.
##  Basic Usage 
    ```python
    from canvas_sdk.v1.data import PatientAdministrativeDocument
    # Get all administrative documents
    documents = PatientAdministrativeDocument.objects.all()
    # Get a specific record by its id
    document = PatientAdministrativeDocument.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    # Get a patient's administrative documents
    patient_documents = PatientAdministrativeDocument.objects.filter(
        patient__id="1eed3ea2a8d546a1b681a2a45de1d790"
    )
    ```
##  Filtering 
Patient administrative documents can be filtered by any attribute that exists on the model.
###  By patient 
    ```python
    from canvas_sdk.v1.data import PatientAdministrativeDocument, Patient
    patient = Patient.objects.get(id="b80b1cdc2e6a4aca90ccebc02e683f35")
    documents = PatientAdministrativeDocument.objects.filter(patient=patient)
    ```
##  Accessing the document file 
The `document_url` property returns a presigned S3 URL for securely accessing the document file, or `None` when no file is present.
    ```python
    from canvas_sdk.v1.data import PatientAdministrativeDocument
    document = PatientAdministrativeDocument.objects.exclude(document="").first()
    # Returns a presigned S3 URL (valid for 1 hour)
    url = document.document_url
    ```
##  The document reference 
Each record also has a [DocumentReference](/sdk/data-document-reference/#the-related-object) pointing back at it — the record that carries the document's coding, category and status, and that represents it in the FHIR API. `document_url` above is the direct route to the file itself; reach for the document reference when you want that surrounding metadata.
Resolve the [ContentType](/sdk/data-content-type/) at runtime from its stable `app_label` and `model` — never hardcode the per-environment `dbid` — and match `object_id` against the record's `dbid`:
    ```python
    from canvas_sdk.v1.data import ContentType, DocumentReference, PatientAdministrativeDocument
    record = PatientAdministrativeDocument.objects.get(
        id="d2194110-5c9a-4842-8733-ef09ea5ead11"
    )
    content_type = ContentType.objects.filter(
        app_label="api", model="patientadministrativedocument"
    ).first()
    document = DocumentReference.objects.filter(
        content_type=content_type, object_id=record.dbid
    ).first()
    ```
> **Info:** `object_id` holds the related record's integer `dbid`, not its UUID `id`. 
##  Document codings 
The `code` field comes from the document's type, which is drawn from a fixed list rather than set freely — either the type selected in Data Integration, or, when a document is created through the FHIR [DocumentReference](/api/documentreference/) endpoint, the LOINC code supplied in `type.coding`, which must match one of the codes below. Every coding uses the LOINC system (`http://loinc.org`). The document types stored as patient administrative documents are:
Document type | Code | Display  
---|---|---  
Advance Beneficiary Notice | 53243-2 | Advanced beneficiary notice  
Advance Directive | 42348-3 | Advance directives  
Commercial Driver License | 53245-7 | Driver license  
Insurance Card Image | 64290-0 | Health insurance card  
Insurer Prior Authorization | 52034-6 | Payer letter  
Patient Agreement | 80570-5 | Agreement  
Patient Consent Documents | 59284-0 | Consent Document  
Power of Attorney | 64298-3 | Power of attorney  
Provider Order | 46209-3 | Provider orders  
Release of Information Request | 101904-1 | Release of Information request  
Uncategorized Administrative Document | 51851-4 | Administrative note  
Workers Compensation Documents | 52070-0 | Workers compensation attachment  
Disability Form | — | none  
Handicap Parking Permit | — | none  
Medicaid Documents | — | none  
Patient Assistance Documents | — | none  
Patient Intake Form | — | none  
Prescription Card Documents | — | none  
> **Warning:** Six of these document types have no coding assigned, so their `code` is `None`. Filtering on `code` silently excludes them — check for a null `code` if you need to catch every administrative document. Because the FHIR endpoint identifies a document's type by its LOINC code, these six can only be created through Data Integration. 
Clinical document types are stored as [UncategorizedClinicalDocument](/sdk/data-uncategorized-clinical-document/) instead. Lab reports, imaging reports and specialist consult reports have their own models, so their codings never appear here.
##  Attributes 
###  PatientAdministrativeDocument 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
patient | [Patient](/sdk/data-patient/#patient)  
originator | [CanvasUser](/sdk/data-canvasuser)  
assigned_by | [CanvasUser](/sdk/data-canvasuser)  
team | [Team](/sdk/data-team/#team)  
integration_task_review | [IntegrationTaskReview](/sdk/data-integration-task/#integrationtaskreview)  
code | DocumentCoding  
name | String  
review_mode | [DocumentReviewMode](/sdk/data-enumeration-types/#documentreviewmode)  
junked | Boolean  
assigned_date | DateTime  
team_assigned_date | DateTime  
original_date | Date  
comment | String  
priority | Boolean  
document | String  
document_url | String (property) — presigned S3 URL or None  
patient_consents | QuerySet[[PatientConsent](/sdk/data-patient-consent/#patientconsent)] — the consents this document is a signed copy of  
###  DocumentCoding 
A coding entry representing the type of document. Also used by [UncategorizedClinicalDocument](/sdk/data-uncategorized-clinical-document/#uncategorizedclinicaldocument).
Field Name | Type  
---|---  
dbid | Integer  
system | String  
version | String  
code | String  
display | String  
user_selected | Boolean
----- END PAGE https://docs.canvasmedical.com/sdk/data-patient-administrative-document/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-patient-consent/
##  Introduction 
The `PatientConsent` model represents documented patient consents in Canvas that ensure legal compliance and protect patient rights. Each `PatientConsent` is linked to a `Patient`, has a category (which is a `PatientConsentCoding`), and optionally a rejection reason (which is a `PatientConsentRejectionCoding`).
##  Usage 
The `PatientConsent` model can be used to find all of the patient consents for a given patient and organization:
    ```python
    >>> from canvas_sdk.v1.data import PatientConsent, Patient, Organization
    >>> patient_1 = Patient.objects.get(id="aebe4d3f5d18410388dc69c4b5169fc3")
    >>> organization_1 = Organization.objects.first()
    >>> patient_1_consents = PatientConsent.objects.filter(patient=patient_1, organization=organization_1)
    >>> print([consent.category.display for consent in patient_1_consents])
    ['Surgical Consent Form', 'Telehealth', 'HIPAA']
    ```
You can also access a patient's consents from the `Patient` model:
    ```python
    >>> from canvas_sdk.v1.data import PatientConsent, Patient
    >>> patient_1 = Patient.objects.get(id="aebe4d3f5d18410388dc69c4b5169fc3")
    >>> patient_1_consents = patient_1.patient_consent.all()
    >>> print([consent.category.display for consent in patient_1_consents])
    ['Surgical Consent Form', 'Telehealth', 'HIPAA']
    ```
And you can also access all of the PatientConsents for a given PatientConsentCoding (aka category):
    ```python
    >>> from canvas_sdk.v1.data import PatientConsentCoding
    >>> coding = PatientConsentCoding.objects.get(code='59284-0', system='LOINC')
    >>> consents = coding.patient_consent.all()
    >>> print([consent.state for consent in consents])
    ['accepted', 'accepted_via_patient_portal', 'rejected']
    ```
Each `PatientConsentCoding` has a `document` field containing the URL to the consent template document:
    ```python
    >>> from canvas_sdk.v1.data import PatientConsentCoding
    >>> coding = PatientConsentCoding.objects.first()
    >>> print(coding.document)
    'consent_templates/hipaa_consent.pdf'
    ```
##  Accessing Document Files 
The `document_url` property returns a presigned S3 URL for securely accessing the blank consent template — the form you send to a patient to collect their consent. The copy the patient signs and returns is a separate record; see Signed consent documents.
    ```python
    from canvas_sdk.v1.data import PatientConsentCoding
    consent_coding = PatientConsentCoding.objects.first()
    # Returns a presigned S3 URL (valid for 1 hour)
    url = consent_coding.document_url
    ```
##  Signed consent documents 
The `PatientConsentCoding.document` above is the blank **template** sent to the patient. The **signed** documents the patient completes and returns are [PatientAdministrativeDocument](/sdk/data-patient-administrative-document/) records, reachable from the consent through the `documents` relation:
    ```python
    from canvas_sdk.v1.data import PatientConsent
    consent = PatientConsent.objects.get(id="8a2b1c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d")
    # Every signed document attached to this consent.
    signed_documents = consent.documents.all()
    # The current signed document (the most recent non-junked one), or None.
    current = consent.active_document
    ```
Each signed document's FHIR [DocumentReference](/sdk/data-document-reference/) is reachable through `document_references`, so a plugin can read the reference (and its `related_object`) in-process without a FHIR call:
    ```python
    from canvas_sdk.v1.data import PatientConsent
    consent = PatientConsent.objects.get(id="8a2b1c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d")
    for reference in consent.document_references:
        url = reference.document_url
    ```
##  Attributes 
###  PatientConsent 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
patient | [Patient](/sdk/data-patient)  
category | PatientConsentCoding  
state | PatientConsentStatus  
effective_date | DateTime  
expired_date | DateTime  
rejection_reason | PatientConsentRejectionCoding  
originator | [CanvasUser](/sdk/data-canvasuser)  
documents | QuerySet[[PatientAdministrativeDocument](/sdk/data-patient-administrative-document/)] — the signed consent documents  
active_document | [PatientAdministrativeDocument](/sdk/data-patient-administrative-document/) (property) — the current signed document, or `None`  
document_references | QuerySet[[DocumentReference](/sdk/data-document-reference/)] (property) — references for the signed documents  
###  PatientConsentCoding 
Field Name | Type  
---|---  
dbid | Integer  
system | String  
version | String  
code | String  
display | String  
user_selected | Boolean  
expiration_rule | PatientConsentExpirationRule  
is_mandatory | Boolean  
is_proof_required | Boolean  
show_in_patient_portal | Boolean  
summary | String  
document | String  
document_url | String (property) — presigned S3 URL  
patient_consent | QuerySet[PatientConsent]  
###  PatientConsentRejectionCoding 
Field Name | Type  
---|---  
dbid | Integer  
system | String  
version | String  
code | String  
display | String  
user_selected | Boolean  
patient_consents | QuerySet[PatientConsent]  
##  Enumeration types 
###  PatientConsentStatus 
Value | Label  
---|---  
accepted | Accepted  
accepted_via_patient_portal | Accepted Via Patient Portal  
rejected | Rejected  
rejected_via_patient_portal | Rejected Via Patient Portal  
###  PatientConsentExpirationRule 
Value | Label  
---|---  
never | Never  
in_one_year | In one year  
end_of_year | End of year
----- END PAGE https://docs.canvasmedical.com/sdk/data-patient-consent/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-patient-group/
##  Introduction 
The `PatientGroup` model represents a named collection of patients. Patients are associated with a group through the `PatientGroupMember` model, which tracks membership along with start/end dates and active status.
##  Basic usage 
To get a patient group by identifier, use the `get` method on the `PatientGroup` model manager:
    ```python
    from canvas_sdk.v1.data.patient_group import PatientGroup
    group = PatientGroup.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    ```
To get the members of a group:
    ```python
    from canvas_sdk.v1.data.patient_group import PatientGroup
    from logger import log
    group = PatientGroup.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    for patient in group.members.all():
        log.info(f"Patient: {patient.id}")
    ```
If you have a patient object, the groups that a patient belongs to can be accessed with the `patient_groups` attribute on a [Patient](/sdk/data-patient/#patient) object:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="a74592ae8a6c4d0ebe0799d3fb3713d1")
    groups = patient.patient_groups.all()
    ```
##  Membership 
The `PatientGroupMember` model represents a patient's membership in a group. To access the membership records for a group:
    ```python
    from canvas_sdk.v1.data.patient_group import PatientGroup, PatientGroupMember
    from logger import log
    group = PatientGroup.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    # Get active members
    active_members = PatientGroupMember.objects.filter(patient_group=group, active=True)
    for membership in active_members:
        log.info(f"Patient: {membership.member.id}, Start: {membership.start_date}")
    ```
##  Filtering 
Patient groups and memberships can be filtered by any attribute that exists on the model.
###  By name 
    ```python
    from canvas_sdk.v1.data.patient_group import PatientGroup
    groups = PatientGroup.objects.filter(name="Diabetes Management")
    ```
###  By active membership 
    ```python
    from canvas_sdk.v1.data.patient_group import PatientGroupMember
    active_memberships = PatientGroupMember.objects.filter(active=True, patient_group__name="Diabetes Management")
    ```
##  Attributes 
###  PatientGroup 
Field Name | Type  
---|---  
id | UUID  
name | String  
members | [Patient](/sdk/data-patient/)[]  
created | DateTime  
modified | DateTime  
patientgroupmember_set | PatientGroupMember[]  
###  PatientGroupMember 
Field Name | Type  
---|---  
created | DateTime  
modified | DateTime  
patient_group | PatientGroup  
member | [Patient](/sdk/data-patient/)  
start_date | DateTime  
end_date | DateTime (nullable)  
locked | Boolean  
active | Boolean  
----- END PAGE https://docs.canvasmedical.com/sdk/data-patient-group/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-patient/
##  Introduction 
The `Patient` model represents an individual receiving care or other health-related services.
##  Basic usage 
To get a patient by identifier, use the `get` method on the `Patient` model manager:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="b80b1cdc2e6a4aca90ccebc02e683f35")
    ```
##  Filtering 
Patients can be filtered by any attribute that exists on the model.
Filtering for patients is done with the `filter` method on the `Patient` model manager.
###  By attribute 
Specify attributes with `filter` to filter by those attributes:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patients = Patient.objects.filter(first_name="Bob", last_name="Loblaw", birth_date="1960-09-22")
    ```
##  Accessing the patient photo 
The `photo_url` property returns a presigned S3 URL for securely accessing the patient's uploaded avatar photo. If the patient has no uploaded avatar, the property returns a default avatar URL instead — so the value is always safe to render without a null check.
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="d7af3e356368446c85b40a5d6ff7288e")
    # Returns a presigned S3 URL (valid for 1 hour), or the default avatar URL when no photo is on file
    url = patient.photo_url
    ```
If you need the underlying `PatientPhoto` record (for example, to read the original `url` or `title`), use the `photo` property:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="d7af3e356368446c85b40a5d6ff7288e")
    photo = patient.photo  # PatientPhoto or None
    if photo:
        print(photo.title)
    ```
##  Accessing educational materials 
If you have a `Patient` object, the educational materials recorded on their notes can be accessed with the `education_material` reverse relation:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="d7af3e356368446c85b40a5d6ff7288e")
    educational_materials = patient.education_material.all()
    ```
##  Attributes 
###  Patient 
Field Name | Type  
---|---  
id | String  
dbid | Integer  
first_name | String  
last_name | String  
birth_date | Date  
sex_at_birth | SexAtBirth  
created | DateTime  
modified | DateTime  
prefix | String  
suffix | String  
middle_name | String  
maiden_name | String  
nickname | String  
sexual_orientation_term | String  
sexual_orientation_code | String  
gender_identity_term | String  
gender_identity_code | String  
preferred_pronouns | String  
biological_race_codes | Array[String]  
cultural_ethnicity_codes | Array[String]  
last_known_timezone | String  
mrn | String  
active | Boolean  
deceased | Boolean  
deceased_datetime | DateTime  
deceased_cause | String  
deceased_comment | String  
other_gender_description | String  
social_security_number | String  
administrative_note | String  
clinical_note | String  
mothers_maiden_name | String  
multiple_birth_indicator | Boolean  
birth_order | Integer  
default_location_id | Integer  
default_provider_id | Integer  
addresses | PatientAddress[]  
allergy_intolerances | [AllergyIntolerance](/sdk/data-allergy-intolerance/#allergyintolerance)[]  
billing_line_items | [BillingLineItem](/sdk/data-billing-line-item/)  
business_line | [BusinessLine](/sdk/data-business-line/)  
care_team_memberships | [CareTeamMembership](/sdk/data-care-team/#careteammembership)[]  
change_medications | [ChangeMedication](/sdk/data-change-medication/#changemedication)[]  
conditions | [Condition](/sdk/data-condition/#condition)[]  
coverages | [Coverage](/sdk/data-coverage/#coverage)[]  
dependent_coverages | [Coverage](/sdk/data-coverage/#coverage)[]  
detected_issues | [DetectedIssue](/sdk/data-detected-issue/#detectedissue)[]  
devices | [Device](/sdk/data-device/#device)[]  
external_identifiers | PatientExternalIdentifier[]  
identification_cards | PatientIdentificationCard[]  
imaging_orders | [ImagingOrder](/sdk/data-imaging/#imagingorder)[]  
imaging_results | [ImagingReport](/sdk/data-imaging/#imagingreport)[]  
imaging_reviews | [ImagingReview](/sdk/data-imaging/#imagingreview)[]  
interviews | [Interview](/sdk/data-questionnaire/#interview)[]  
lab_orders | [LabOrder](/sdk/data-labs/#laborder)[]  
lab_reports | [LabReport](/sdk/data-labs/#labreport)[]  
lab_reviews | [LabReview](/sdk/data-labs/#labreview)[]  
medications | [Medication](/sdk/data-medication/#medication)[]  
metadata | PatientMetadata[]  
observations | [Observation](/sdk/data-observation/#observation)[]  
photos | PatientPhoto[]  
preferred_pharmacy | JSON  
preferred_pharmacies | JSON  
protocol_overrides | [ProtocolOverride](/sdk/data-protocol-override/#protocoloverride)[]  
settings | PatientSetting  
subscribed_coverages | [Coverage](/sdk/data-coverage/#coverage)[]  
tasks | [Task](/sdk/data-task/#task)[]  
telecom | PatientContactPoint[]  
contacts | PatientContactPerson[]  
related_contacts | PatientContactPerson[] — contacts on _other_ patients that reference this one  
user | [CanvasUser](/sdk/data-canvasuser/)[]  
patient_groups | [PatientGroup](/sdk/data-patient-group/)[]  
chart_section_reviews | [ChartSectionReview](/sdk/data-chart-section-review/#chartsectionreview)[]  
visual_exam_findings | [VisualExamFinding](/sdk/data-visual-exam-finding/#visualexamfinding)[]  
vital_sign_readings | [VitalSignReading](/sdk/data-vital-sign-reading/#vitalsignreading)[]  
assessments | [Assessment](/sdk/data-assessment/#assessment)[]  
patient_visits | [ExternalVisit](/sdk/data-external-event/#externalvisit)[]  
patient_events | [ExternalEvent](/sdk/data-external-event/#externalevent)[]  
medication_statements | [MedicationStatement](/sdk/data-medication-statement/#medicationstatement)[]  
diagnostic_reports | DiagnosticReport[]  
medication_history_medications | [MedicationHistoryMedication](/sdk/data-medication-history/#medicationhistorymedication)[]  
medication_history_responses | [MedicationHistoryResponse](/sdk/data-medication-history/#medicationhistoryresponse)[]  
payments | [BulkPatientPosting](/sdk/data-posting/#bulkpatientposting)[]  
protocol_currents | [ProtocolCurrent](/sdk/data-protocol-current/)[]  
stopped_medications | [StopMedicationEvent](/sdk/data-stop-medication-event/#stopmedicationevent)[]  
banner_alerts | [BannerAlert](/sdk/data-banner-alert/#banneralert)[]  
immunizations | [Immunization](/sdk/data-immunization/#immunization)[]  
immunization_statements | [ImmunizationStatement](/sdk/data-immunization/#immunizationstatement)[]  
integration_tasks | [IntegrationTask](/sdk/data-integration-task/#integrationtask)[]  
installment_plans | [InstallmentPlan](/sdk/data-claim/#installmentplan)[]  
uncategorized_clinical_document_reviews | [UncategorizedClinicalDocumentReview](/sdk/data-uncategorized-clinical-document/#uncategorizedclinicaldocumentreview)[]  
patient_consent | [PatientConsent](/sdk/data-patient-consent/#patientconsent)[]  
goals | [Goal](/sdk/data-goal/#goal)[]  
updategoals | [UpdateGoal](/sdk/data-goal/#updategoal)[]  
instructions | [Instruction](/sdk/data-instruction/#instruction)[]  
appointments | [Appointment](/sdk/data-appointment/#appointment)[]  
notes | [Note](/sdk/data-note/#note)[]  
prescriptions | [Prescription](/sdk/data-prescription/#prescription)[]  
refill_requests | [RefillRequest](/sdk/data-refill-request/#refillrequest)[]  
referral_reviews | [ReferralReview](/sdk/data-referral/#referralreview)[]  
referral_reports | [ReferralReport](/sdk/data-referral/#referralreport)[]  
invoices | Invoice[]  
education_material | [EducationalMaterial](/sdk/data-educational-material/#educationalmaterial)[]  
procedures | [Procedure](/sdk/data-procedure/#procedure)[]  
family_histories | [FamilyHistory](/sdk/data-family-history/#familyhistory)[]  
histories_of_present_illness | [HistoryOfPresentIllness](/sdk/data-history-present-illness/#historyofpresentillness)[]  
plans | [Plan](/sdk/data-plan/#plan)[]  
follow_ups | [FollowUp](/sdk/data-follow-up/#followup)[]  
reasons_for_visit | [ReasonForVisit](/sdk/data-reason-for-visit/#reasonforvisit)[]  
removed_allergies | [RemoveAllergyEvent](/sdk/data-remove-allergy-event/#removeallergyevent)[]  
resolved_conditions | [ResolveConditionEvent](/sdk/data-resolve-condition-event/#resolveconditionevent)[]  
cancel_prescriptions | [CancelPrescription](/sdk/data-cancel-prescription/#cancelprescription)[]  
cancel_prescription_responses | [CancelPrescriptionResponse](/sdk/data-cancel-prescription-response/#cancelprescriptionresponse)[]  
prescription_change_requests | [PrescriptionChangeRequest](/sdk/data-prescription-change-request/#prescriptionchangerequest)[]  
prescription_change_responses | [PrescriptionChangeResponse](/sdk/data-prescription-change-response/#prescriptionchangeresponse)[]  
###  PatientAddress 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
line1 | String  
line2 | String  
city | String  
district | String  
state_code | String  
postal_code | String  
use | [AddressUse](/sdk/data-enumeration-types/#addressuse)  
type | [AddressType](/sdk/data-enumeration-types/#addresstype)  
longitude | Float  
latitude | Float  
start | Date  
end | Date  
country | String  
state | [AddressState](/sdk/data-enumeration-types/#addressstate)  
patient | Patient  
    ```python
    from canvas_sdk.v1.data.patient import Patient
    from logger import log
    patient_id = "d7af3e356368446c85b40a5d6ff7288e"
    patient = Patient.objects.get(id=patient_id)
    patient_addresses = patient.addresses.all()
    for addr in patient_addresses:
      log.info(f"Patient address: {addr.city}, {addr.state_code}, {addr.postal_code}") # Seattle, WA, 98118
    ```
###  PatientContactPoint 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
system | [ContactPointSystem](/sdk/data-enumeration-types/#contactpointsystem)  
value | String  
use | String  
use_notes | String  
rank | Integer  
state | [ContactPointState](/sdk/data-enumeration-types/#contactpointstate)  
patient | Patient  
has_consent | Boolean  
last_verified | DateTime  
verification_token | String  
opted_out | Boolean  
    ```python
    from canvas_sdk.v1.data.patient import Patient
    from logger import log
    patient_id = "d7af3e356368446c85b40a5d6ff7288e"
    patient = Patient.objects.get(id=patient_id)
    patient_contacts = patient.telecom.all()
    for contact in patient_contacts:
       log.info(f"Patient contact: {contact.system} - {contact.value}") # phone - 5555555555
    ```
###  PatientExternalIdentifier 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
patient | Patient  
use | String  
identifier_type | String  
system | String  
value | String  
issued_date | Date  
expiration_date | Date  
    ```python
    from canvas_sdk.v1.data.patient import Patient
    from logger import log
    patient_id = "d7af3e356368446c85b40a5d6ff7288e"
    patient = Patient.objects.get(id=patient_id)
    patient_external_identifiers = patient.external_identifiers.all()
    for identifier in patient_external_identifiers:
       log.info(f"Patient external identifier: {identifier.system}, {identifier.value}")  # https://www.example.com - abc123
    ```
###  PatientSetting 
Field Name | Type  
---|---  
dbid | Integer  
created | DateTime  
modified | DateTime  
patient | Patient  
name | String  
value | JSON  
###  PatientMetadata 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
patient | Patient  
key | String  
value | String  
    ```python
    from canvas_sdk.v1.data.patient import Patient
    from logger import log
    patient_id = "d7af3e356368446c85b40a5d6ff7288e"
    patient = Patient.objects.get(id=patient_id)
    patient_metadata = patient.metadata.all()
    for metadata in patient_metadata:
       log.info(f"Patient metadata: {metadata.key}, {metadata.value}") # favorite_color - red
    ```
###  PatientPhoto 
Represents a patient's uploaded avatar photo.
Field Name | Type  
---|---  
dbid | Integer  
created | DateTime  
modified | DateTime  
patient | Patient  
url | String  
title | String  
    ```python
    from canvas_sdk.v1.data.patient import Patient
    from logger import log
    patient = Patient.objects.get(id="d7af3e356368446c85b40a5d6ff7288e")
    for photo in patient.photos.all():
        log.info(f"Photo: {photo.title}, stored at: {photo.url}")
    ```
###  PatientIdentificationCard 
Represents a patient identification card image (e.g., driver's license, insurance card).
Field Name | Type  
---|---  
dbid | Integer  
created | DateTime  
modified | DateTime  
patient | Patient  
image | String  
title | String  
active | Boolean  
image_url | String (property) — presigned S3 URL  
    ```python
    from canvas_sdk.v1.data.patient import Patient
    from logger import log
    patient = Patient.objects.get(id="d7af3e356368446c85b40a5d6ff7288e")
    for card in patient.identification_cards.filter(active=True):
        log.info(f"ID card: {card.title}, URL: {card.image_url}")
    ```
###  PatientFacilityAddress 
Field Name | Type  
---|---  
patientaddress | PatientAddress  
facility | Facility  
room_number | String  
###  PatientContactPerson 
One of the patient's contacts — an emergency contact, next-of-kin, or other related person. A contact either holds the person's details directly, or references another Canvas patient through `related_patient`; when it does, that patient's own details supersede the values stored here.
`id` is the value the [Patient effect](/sdk/effect-patient/#managing-patient-contacts) takes as `contact_identifier` when modifying or removing a contact.
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
patient | Patient  
name | String  
phone_number | String  
email | String  
comments | String  
related_patient | Patient  
categories | PatientContactCategory[]  
    ```python
    from canvas_sdk.v1.data import PatientContactPerson
    from logger import log
    contacts = PatientContactPerson.objects.filter(
        patient__id="d7af3e356368446c85b40a5d6ff7288e"
    ).select_related("related_patient").prefetch_related("categories__category")
    for contact in contacts:
        who = contact.related_patient.first_name if contact.related_patient else contact.name
        codings = ", ".join(link.category.code for link in contact.categories.all())
        log.info(f"Contact: {who} ({codings})")  # Contact: Jane (EMC)
    ```
###  PatientContactCategory 
Links one of the patient's contacts to one of the category codings the instance defines.
Field Name | Type  
---|---  
dbid | Integer  
created | DateTime  
modified | DateTime  
contact_person | PatientContactPerson  
category | ContactCategory  
###  ContactCategory 
A contact-category coding available in this Canvas instance — the set a contact's relationship can be drawn from.
Use this to look up a coding before writing it with the [Patient effect](/sdk/effect-patient/#patientcontactcategory). Writing a coding that does not appear here is rejected rather than created, so querying this model first is how you find out what the instance actually has.
Field Name | Type  
---|---  
dbid | Integer  
name | String  
code | String  
system | String  
protected | Boolean  
    ```python
    from canvas_sdk.v1.data import ContactCategory
    from logger import log
    for coding in ContactCategory.objects.order_by("code"):
        log.info(f"{coding.code} / {coding.system} — {coding.name}")  # EMC / INTERNAL — Emergency contact
    ```
##  Enumeration types 
###  SexAtBirth 
Value | Label  
---|---  
F | female  
M | male  
O | other  
UNK | unknown  
"" (empty string) | ""  
##  Computed Properties 
###  Patient 
  - `full_name`: The full name of the patient, combining first, middle, and last names.
  - `preferred_pharmacy`: The patient's preferred pharmacy for medication fulfillment.
  - `preferred_full_name`: The patient's preferred full name, if different from the legal name.
  - `preferred_first_name`: The patient's preferred first name, if different from the legal first name.
  - `primary_phone_number`: The patient's primary contact number.
  - `photo`: The patient's first uploaded avatar PatientPhoto, if any.
  - `photo_url`: A presigned URL for the patient's avatar photo, or the default avatar URL when no photo is set.
----- END PAGE https://docs.canvasmedical.com/sdk/data-patient/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-payor-specific-charge/
##  Introduction 
The `PayorSpecificCharge` model represents charges specific to a [Transactor](/sdk/data-coverage/#transactor) in Canvas.
##  Usage 
The `PayorSpecificCharge` model can be used to find all of the charges specific to a single `Transactor`:
    ```python
    >>> from canvas_sdk.v1.data import PayorSpecificCharge, Transactor
    >>> aetna = Transactor.objects.get(payer_id="60054")
    >>> aetna_charges = PayorSpecificCharge.objects.filter(transactor=aetna)
    >>> print([charge.charge_amount for charge in aetna_charges])
    [150.00, 40.00, 99.99]
    ```
You can also access a transactor's specific charges from the `Transactor` model:
    ```python
    >>> from canvas_sdk.v1.data import Transactor
    >>> aetna = Transactor.objects.get(payer_id="60054")
    >>> aetna_charges = aetna.specific_charges.all()
    >>> print([charge.charge_amount for charge in aetna_charges])
    [150.00, 40.00, 99.99]
    ```
`
##  Attributes 
###  PayorSpecificCharge 
Field Name | Type  
---|---  
dbid | Integer  
transactor | [Transactor](/sdk/data-coverage/#transactor)  
charge | [ChargeDescriptionMaster](/sdk/data-charge-description-master)  
charge_amount | Decimal  
effective_date | Date  
end_date | Date  
part_of_capitated_set | Boolean
----- END PAGE https://docs.canvasmedical.com/sdk/data-payor-specific-charge/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-plan/
##  Introduction 
The `Plan` model represents a Plan (plan of care) recorded on a Note, and is always associated with a Note and a Patient. It is the anchor for the [Plan](/sdk/commands/#plan) command.
##  Basic usage 
To get a plan by identifier, use the `get` method on the `Plan` model manager:
    ```python
    from canvas_sdk.v1.data.plan import Plan
    plan = Plan.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    ```
If you have a patient object, or note object, the plans for a patient or note can be accessed with the `plans` attribute on a `Patient` or `Note` object:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    from canvas_sdk.v1.data.note import Note
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    plans = patient.plans.all()
    note = Note.objects.get(id="89992c23-c298-4118-864a-26cb3e1ae822")
    plans = note.plans.all()
    ```
##  Reading the narrative 
The plan text is exposed through the `narrative` property:
    ```python
    from canvas_sdk.v1.data.plan import Plan
    plan = Plan.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    text = plan.narrative
    ```
##  Filtering 
Plans can be filtered by any attribute that exists on the model.
###  Committed plans 
The `committed` method returns plans that have been committed and not entered in error:
    ```python
    from canvas_sdk.v1.data.plan import Plan
    committed_plans = Plan.objects.committed()
    ```
##  Attributes 
###  Plan 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
originator | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
patient | [Patient](/sdk/data-patient/#patient)  
note | [Note](/sdk/data-note)  
narrative | String  
----- END PAGE https://docs.canvasmedical.com/sdk/data-plan/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-plugin-command/
##  Introduction 
The `PluginCommand` model exposes the custom commands a plugin registers in its `CANVAS_MANIFEST.json`. Use it to read back a registered command's `label` and `section` instead of reconstructing display text from its camelCase `command_key`.
##  Basic usage 
To get a plugin command by identifier, use the `get` method on the `PluginCommand` model manager:
    ```python
    from canvas_sdk.v1.data.plugin_command import PluginCommand
    plugin_command = PluginCommand.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    ```
##  Filtering 
Plugin commands can be filtered by any attribute that exists on the model.
Filtering for plugin commands is done with the `filter` method on the `PluginCommand` model manager.
###  By attribute 
Specify an attribute with `filter` to filter by that attribute:
    ```python
    from canvas_sdk.v1.data.plugin_command import PluginCommand
    # Find all plugin commands in a specific chart section
    plugin_commands = PluginCommand.objects.filter(section="assessment")
    ```
###  By command key 
To find a registered command by the key declared in the manifest, filter on `command_key` — or on `schema_key`, which holds the same value:
    ```python
    from canvas_sdk.v1.data.plugin_command import PluginCommand
    plugin_command = PluginCommand.objects.filter(command_key="riskAssessment").first()
    if plugin_command:
        print(f"Label: {plugin_command.label}")
        print(f"Section: {plugin_command.section}")
    ```
##  Attributes 
###  PluginCommand 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
name | String  
command_key | String  
schema_key | String  
label | String  
section | String  
plugin_name | String  
  - **id** : The unique UUID identifier for the plugin command.
  - **dbid** : The internal database primary key.
  - **name** : The registered name of the command (e.g., `RiskAssessment`).
  - **command_key** : The command key declared in the plugin's manifest (e.g., `riskAssessment`). There is exactly one row per `command_key`: reinstalling or upgrading the plugin updates that row in place, so a command always carries its current `label` and `section`.
  - **schema_key** : Always equal to `command_key`. It exists as its own field because chart command lines use the same name — see [`Command.schema_key`](/sdk/data-command/#command), which plugin authors query with `Command.objects.filter(schema_key="riskAssessment")`. Two installed plugins cannot declare the same key; the second install fails with a validation error.
  - **label** : The user-friendly display label for the command (e.g., `Risk Assessment`).
  - **section** : The chart section where the command appears: `subjective`, `objective`, `assessment`, `plan`, `procedures`, `history`, or `internal`.
  - **plugin_name** : The name of the plugin that registered the command.
----- END PAGE https://docs.canvasmedical.com/sdk/data-plugin-command/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-posting/
##  Introduction 
This module defines models related to payments and postings associated with healthcare claims.
##  Basic usage 
To retrieve a posting by ID:
    ```python
    from canvas_sdk.v1.data.posting import BasePosting
    posting = BasePosting.objects.get(dbid=1234)
    ```
To retrieve all active postings for a given claim:
    ```python
    from canvas_sdk.v1.data.claim import Claim
    claim = Claim.objects.get(id="<uuid>")
    claim_postings = claim.postings.active()
    ```
##  Attributes 
###  BasePosting 
Base model for aggregating multiple line item-level transactions (payments, adjustments, transfers) associated with a claim.
Field Name | Type  
---|---  
dbid | Integer  
corrected_posting | BasePosting  
claim | [Claim](/sdk/data-claim/#claim)  
payment_collection | PaymentCollection  
description | String  
entered_in_error | [CanvasUser](/sdk/data-canvasuser/)  
created | DateTime  
modified | DateTime  
correction_postings | QuerySet[BasePosting]  
**Computed Properties** :
  - `paid_amount`: Total paid
  - `contractual_adjusted_amount`: Adjustments marked as write-offs
  - `non_write_off_adjusted_amount`: Non-write-off adjustments
  - `transferred_amount`: Total transferred
  - `transferred_to_patient_amount`: Portion transferred to patient
  - `transferred_to_coverage_amount`: Portion transferred to another coverage
  - `adjusted_and_transferred_amount`: Combined adjusted and transferred amount
  - `posted_amount`: Total of payments and write-offs
###  CoveragePosting 
Represents an insurance payment or adjustment associated with a claim's coverage.
Field Name | Type  
---|---  
remittance | BaseRemittanceAdvice  
claim_coverage | [ClaimCoverage](/sdk/data-claim/#claimcoverage)  
crossover_carrier | String  
crossover_id | String  
payer_icn | String  
position_in_era | Integer  
###  PatientPosting 
Represents patient-side payments or adjustments, including links to copays or patient-level discounts.
Field Name | Type  
---|---  
claim_patient | [ClaimPatient](/sdk/data-claim/#claimpatient)  
patient_payment | BulkPatientPosting  
copay | BulkPatientPosting  
**Computed Properties** :
  - `discounted_amount`: Discount applied
  - `charges_amount`: Discount + paid amount
###  BulkPatientPosting 
Aggregates bulk patient payments on multiple claims.
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
payment_collection | PaymentCollection  
total_paid | Decimal  
created | DateTime  
modified | DateTime  
discount | Discount  
payer | [Patient](/sdk/data-patient/)  
postings | QuerySet[PatientPosting]  
copays | QuerySet[PatientPosting]  
**Computed Properties** :
  - `total_posted_amount`: Sum of all posted amounts
  - `discounted_amount`: Sum of discounted amounts
###  BaseRemittanceAdvice 
Represents shared data for both electronic and manual remittance advice.
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
payment_collection | PaymentCollection  
total_paid | Decimal  
created | DateTime  
modified | DateTime  
transactor | [Transactor](/sdk/data-coverage/#transactor)  
era_id | String  
postings | QuerySet[CoveragePosting]  
**Computed Properties** :
  - `total_posted_amount`: Sum of all posted amounts
###  PaymentCollection 
Captures metadata about the method and details of a collected payment.
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
total_collected | Decimal  
method | PostingMethods  
check_number | String  
check_date | Date  
deposit_date | Date  
description | String  
created | DateTime  
modified | DateTime  
postings | QuerySet[BasePosting]  
###  NewLineItemPayment 
Represents a payment applied to a billing line item within a claim.
Field Name | Type  
---|---  
dbid | Integer  
posting | BasePosting  
billing_line_item | [BillingLineItem](/sdk/data-billing-line-item/)  
amount | Decimal  
charged | Decimal  
created | DateTime  
modified | DateTime  
###  NewLineItemAdjustment 
Represents an adjustment applied to a billing line item.
Field Name | Type  
---|---  
dbid | Integer  
posting | BasePosting  
billing_line_item | [BillingLineItem](/sdk/data-billing-line-item/)  
amount | Decimal  
code | String  
group | String  
deviated_from_posting_ruleset | Boolean  
write_off | Boolean  
created | DateTime  
modified | DateTime  
###  LineItemTransfer 
Represents a transfer of a line item balance to another coverage or patient.
Field Name | Type  
---|---  
dbid | Integer  
posting | BasePosting  
billing_line_item | [BillingLineItem](/sdk/data-billing-line-item/)  
amount | Decimal  
code | String  
group | String  
deviated_from_posting_ruleset | Boolean  
transfer_to | [ClaimCoverage](/sdk/data-claim/#claimcoverage)  
transfer_to_patient | Boolean  
created | DateTime  
modified | DateTime  
###  Discount 
Represents a discount applied to a claim or patient posting, linked by adjustment group and code.
Field Name | Type  
---|---  
dbid | Integer  
name | String  
adjustment_group | String  
adjustment_code | String  
discount | Decimal  
created | DateTime  
modified | DateTime  
patient_postings | QuerySet[BulkPatientPosting]  
##  Enumeration types 
###  PostingMethods 
Value | Label  
---|---  
cash | Cash  
check | Check  
card | Card  
other | Other
----- END PAGE https://docs.canvasmedical.com/sdk/data-posting/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-practicelocation/
##  Introduction 
The `PracticeLocation` model lists all the clinical practice locations that fall under an [Organization](/sdk/data-organization).
##  Basic usage 
To query a `PracticeLocation` by name, the `filter` method can be used like so:
    ```python
    from canvas_sdk.v1.data.practicelocation import PracticeLocation
    practice_location = PracticeLocation.objects.filter(full_name__icontains="downtown")
    ```
To retrieve a list of all practice locations:
    ```python
    from canvas_sdk.v1.data.practicelocation import PracticeLocation
    practice_locations = PracticeLocation.objects.all()
    ```
To query addresses that are associated with a `PracticeLocation`, related `PracticeLocationAddress` model instances can be accessed by using the `addresses` attribute. For exmample:
    ```python
    from canvas_sdk.v1.data.practicelocation import PracticeLocation
    practice_location = PracticeLocation.objects.first()
    practice_location_addresses = practice_location.addresses.all()
    ```
Each `PracticeLocation` has location-specific settings that control certain behavior within the EMR application. To retrieve the available settings for a `PracticeLocation` instance, the `settings` attribute can be used to retrieve a list of names:
    ```python
    from canvas_sdk.v1.data.practicelocation import PracticeLocation
    practice_location = PracticeLocation.objects.first()
    available_settings = practice_location.settings.values_list('name', flat=True)
    ```
Additionally, a setting's value can be found by accessing the `value` attribute on the `PracticeLocationSetting`:
    ```python
    from canvas_sdk.v1.data.practicelocation import PracticeLocation
    practice_location = PracticeLocation.objects.first()
    preferred_lab_partner_text = practice_location.settings.get(name="preferredLabPartner").value
    ```
Please note that the content of each `value` field can contain any value that is JSON-serializable, which includes string values. This means that `value` could be any of the Python types `string`, `list` or `dict`.
##  Attributes 
###  PracticeLocation 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
organization | [Organization](/sdk/data-organization/#organization)  
place_of_service_code | String  
full_name | String  
short_name | String  
background_image_url | String  
background_gradient | String  
active | Boolean  
npi_number | String  
bill_through_organization | Boolean  
tax_id | String  
tax_id_type | [TaxIDType](/sdk/data-enumeration-types/#taxidtype)  
billing_location_name | String  
group_npi_number | String  
taxonomy_number | String  
include_zz_qualifier | Boolean  
addresses | PracticeLocationAddress  
settings | PracticeLocationSetting  
telecom | PracticeLocationContactPoint  
###  PracticeLocationAddress 
Field Name | Type  
---|---  
dbid | Integer  
practice_location | PracticeLocation  
line1 | String  
line2 | String  
city | String  
district | String  
state_code | String  
postal_code | String  
use | [AddressUse](/sdk/data-enumeration-types/#addressuse)  
type | [AddressType](/sdk/data-enumeration-types/#addresstype)  
longitude | Float  
latitude | Float  
start | Date  
end | Date  
country | String  
state | [AddressState](/sdk/data-enumeration-types/#addressstate)  
###  PracticeLocationSetting 
Field Name | Type  
---|---  
dbid | Integer  
practice_location | PracticeLocation  
name | String  
value | JSON  
##  PracticeLocationContactPoint 
The `PracticeLocationContactPoint` model represents a contact method (such as phone, email, or fax) for a Practice Location. Multiple contact points can be associated with a single Practice Location, each with its own type, use, and status.
###  Attributes 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
practice_location | PracticeLocation  
system | [ContactPointSystem](/sdk/data-enumeration-types/#contactpointsystem)  
value | String  
use | [ContactPointUse](/sdk/data-enumeration-types/#contactpointuse)  
use_notes | String  
rank | Integer  
state | [ContactPointState](/sdk/data-enumeration-types/#contactpointstate)
----- END PAGE https://docs.canvasmedical.com/sdk/data-practicelocation/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-prescription-change-request/
##  Introduction 
The `PrescriptionChangeRequest` model represents an incoming Surescripts (NCPDP SCRIPT) request to change a prescription — for example, a generic substitution, a prior-authorization requirement, or a script clarification. Each request carries the raw request payload in its `content` attribute, the medication codings that describe the drug in question (`PrescriptionChangeRequestCoding`), and a reference to the original prescription it relates to.
Because a `PrescriptionChangeRequest` originates from the pharmacy, its `patient`, `note`, and `staff` associations are nullable and may be unset. The provider's approve/deny decision is recorded as a [PrescriptionChangeResponse](/sdk/data-prescription-change-response/), which links back to the request and is available through the request's `response` reverse relation.
##  Basic usage 
To get a prescription change request by identifier, use the `get` method on the `PrescriptionChangeRequest` model manager:
    ```python
    from canvas_sdk.v1.data import PrescriptionChangeRequest
    change_request = PrescriptionChangeRequest.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    ```
##  Related data 
A change request's medication codings are available through the `codings` reverse relation, and the responses recorded against it are available through the `response` reverse relation:
    ```python
    from canvas_sdk.v1.data import PrescriptionChangeRequest
    from logger import log
    change_request = PrescriptionChangeRequest.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    for coding in change_request.codings.all():
        log.info(f"system:  {coding.system}")
        log.info(f"code:    {coding.code}")
        log.info(f"display: {coding.display}")
    responses = change_request.response.all()
    ```
The `PrescriptionChangeRequestCoding` entries represent the coding of the medication in question (for example, FDB or RxNorm), with an unstructured fallback whose `display` carries the drug description text when no structured code is available.
##  Message content 
The `message_id` and `content` attributes carry the details of the inbound eRx message.
`message_id` is the eRx (NCPDP SCRIPT / Surescripts) message identifier of the inbound change request.
`content` is a JSON field holding the parsed inbound NCPDP SCRIPT change-request payload. It is a free-form, unstructured representation whose exact shape can vary between messages, typically including the pharmacy, the prescriber as reported by the sender, and the dispensed medication details (drug description, NDC, quantity, and similar). `content` defaults to an empty object (`{}`), so it is safe to call `.get()` on, but individual keys may be absent — plugins should access it defensively.
##  Change types 
The `type_code` attribute identifies the kind of change the pharmacy is requesting:
Code | Description  
---|---  
G | Generic Substitution  
P | Prior Authorization Required  
S | Therapeutic Interchange/Substitution  
D | Drug Use Evaluation  
S | Script Clarification  
OS | Pharmacy is out of stock  
U | Prescriber Authorization  
`S` really does carry two meanings. Canvas maps it to both Therapeutic Interchange/Substitution and Script Clarification, so the two are indistinguishable from `type_code` alone.
The `sub_type_code` attribute further qualifies the request. It is nullable and currently supports:
Code | Description  
---|---  
A | Confirm Prescriber State License  
##  Attributes 
###  PrescriptionChangeRequest 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
patient | [Patient](/sdk/data-patient/#patient)  
note | [Note](/sdk/data-note)  
staff | [Staff](/sdk/data-staff/#staff)  
message_id | String  
original_prescription | [Prescription](/sdk/data-prescription/#prescription)  
type_code | PrescriptionChangeRequestType  
sub_type_code | PrescriptionChangeRequestSubType  
content | JSON  
codings | PrescriptionChangeRequestCoding[]  
response | [PrescriptionChangeResponse](/sdk/data-prescription-change-response/)[]  
###  PrescriptionChangeRequestCoding 
Field Name | Type  
---|---  
dbid | Integer  
change_request | PrescriptionChangeRequest  
system | String  
version | String  
code | String  
display | String  
user_selected | Boolean  
##  Enumeration types 
###  PrescriptionChangeRequestType 
Name | Value | Label  
---|---|---  
GENERIC | G | Generic Substitution  
PRIOR | P | Prior Authorization Required  
SUBSTITUTION | S | Therapeutic Interchange/Substitution  
DRUG | D | Drug Use Evaluation  
OUTOFSTOCK | OS | Pharmacy is out of stock  
AUTHORIZATION | U | Prescriber Authorization  
###  PrescriptionChangeRequestSubType 
Name | Value | Label  
---|---|---  
LICENSE | A | Confirm Prescriber State License
----- END PAGE https://docs.canvasmedical.com/sdk/data-prescription-change-request/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-prescription-change-response/
##  Introduction 
The `PrescriptionChangeResponse` model is the anchor for the ApproveChange and DenyChange commands — a response to a Surescripts prescription change request, recorded on a Note.
##  Basic usage 
To get a prescription change response by identifier, use the `get` method on the `PrescriptionChangeResponse` model manager:
    ```python
    from canvas_sdk.v1.data import PrescriptionChangeResponse
    response = PrescriptionChangeResponse.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    ```
If you have a patient object, the prescription change responses for a patient can be accessed with the `prescription_change_responses` attribute:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    responses = patient.prescription_change_responses.all()
    ```
The same attribute is available on a medication:
    ```python
    from canvas_sdk.v1.data import Medication
    medication = Medication.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    responses = medication.prescription_change_responses.all()
    ```
##  Committed records 
The `committed` method returns responses that have been committed and not entered in error:
    ```python
    from canvas_sdk.v1.data import PrescriptionChangeResponse
    committed_responses = PrescriptionChangeResponse.objects.committed()
    ```
##  Attributes 
###  PrescriptionChangeResponse 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
originator | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
patient | [Patient](/sdk/data-patient/#patient)  
note | [Note](/sdk/data-note)  
medication | [Medication](/sdk/data-medication)  
response_type | PrescriptionChangeResponseType  
status | PrescriptionChangeResponseStatus  
denied_medication | String  
refills | Integer  
note_to_pharmacist | String  
approved_drug_index | Integer  
reason_code | String  
message_id | String  
prior_authorization_number | String  
request | [PrescriptionChangeRequest](/sdk/data-prescription-change-request/)  
##  Enumeration types 
###  PrescriptionChangeResponseType 
Name | Value  
---|---  
APPROVED | A  
DENIED | D  
###  PrescriptionChangeResponseStatus 
Name | Value  
---|---  
OPEN | open  
PENDING | pending  
ULTIMATELY_ACCEPTED | ultimately-accepted  
ERROR | error  
----- END PAGE https://docs.canvasmedical.com/sdk/data-prescription-change-response/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-prescription/
##  Introduction 
The `Prescription` model represents a prescription for a medication that has been written for a patient. Prescriptions track the full lifecycle of a medication order, including dosage details, pharmacy information, and electronic prescribing status.
##  Basic usage 
To get a prescription by identifier, use the `get` method on the `Prescription` model manager:
    ```python
    from canvas_sdk.v1.data.prescription import Prescription
    prescription = Prescription.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    ```
If you have a patient object, the prescriptions for a patient can be accessed with the `prescriptions` attribute on a `Patient` object:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    prescriptions = patient.prescriptions.all()
    ```
If you have a patient ID, you can get the prescriptions for the patient with the `for_patient` method on the `Prescription` model manager:
    ```python
    from canvas_sdk.v1.data.prescription import Prescription
    patient_id = "1eed3ea2a8d546a1b681a2a45de1d790"
    prescriptions = Prescription.objects.for_patient(patient_id)
    ```
##  Filtering 
Prescriptions can be filtered by any attribute that exists on the model.
Filtering for prescriptions is done with the `filter` method on the `Prescription` model manager.
###  By attribute 
Specify an attribute with `filter` to filter by that attribute:
    ```python
    from canvas_sdk.v1.data.prescription import Prescription, PrescriptionStatus
    prescriptions = Prescription.objects.filter(status=PrescriptionStatus.OPEN)
    ```
    ```python
    from canvas_sdk.v1.data.prescription import Prescription, PrescriptionResponse
    approved_prescriptions = Prescription.objects.filter(response_type=PrescriptionResponse.APPROVED)
    ```
###  Active prescriptions 
The `active` method returns committed prescriptions that have not been denied:
    ```python
    from canvas_sdk.v1.data.prescription import Prescription
    active_prescriptions = Prescription.objects.active()
    ```
###  Committed prescriptions 
The `committed` method returns prescriptions that have been committed and not entered in error:
    ```python
    from canvas_sdk.v1.data.prescription import Prescription
    committed_prescriptions = Prescription.objects.committed()
    ```
##  Attributes 
###  Prescription 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
patient | [Patient](/sdk/data-patient/)  
note | [Note](/sdk/data-note/)  
prescriber | [Staff](/sdk/data-staff/)  
supervising_provider | [Staff](/sdk/data-staff/)  
medication | [Medication](/sdk/data-medication/)  
compound_medication | [CompoundMedication](/sdk/data-compound-medication/)  
previous_medication | [Medication](/sdk/data-medication/)  
indications | [Assessment](/sdk/data-assessment/)[]  
related_refill | Prescription  
refill_request | [RefillRequest](/sdk/data-refill-request/)  
status | PrescriptionStatus  
response_type | PrescriptionResponse  
is_refill | Boolean  
is_adjustment | Boolean  
is_epcs | Boolean  
generic_substitutions_allowed | Boolean  
written_date | DateTime  
dispensed_date | DateTime  
end_date | Date  
end_date_original_input | String  
sig_original_input | String  
dose_form | String  
dose_route | String  
dose_quantity | Float  
dose_frequency | Float  
dose_frequency_interval | String  
maximum_daily_dose | String  
potency_quantity | Float  
dispense_quantity | Float  
duration_in_days | Integer  
count_of_refills_allowed | Integer  
note_to_pharmacist | String  
pharmacy_name | String  
pharmacy_ncpdp_id | String  
pharmacy_address | String  
pharmacy_phone_number | String  
pharmacy_fax_number | String  
pharmacy_is_read_only | Boolean  
message_id | String  
prescription_order_number | String  
reason_code | String  
error_message | String  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
originator | [CanvasUser](/sdk/data-canvasuser)  
created | DateTime  
modified | DateTime  
cancel_prescriptions | [CancelPrescription](/sdk/data-cancel-prescription/#cancelprescription)[]  
change_requests | [PrescriptionChangeRequest](/sdk/data-prescription-change-request/#prescriptionchangerequest)[]  
##  Enumeration types 
###  PrescriptionStatus 
Enum | Value | Label  
---|---|---  
OPEN | open | Open  
PENDING | pending | Pending  
ACCEPTED | ultimately-accepted | Ultimately Accepted  
ERROR | error | Error  
CANCEL_REQUESTED | cancel-requested | Cancel Requested  
CANCELED | canceled | Canceled  
CANCEL_DENIED | cancel-denied | Cancel Denied  
RECEIVED | received | Received by DrFirst  
SIGNED | signed | Signed  
INQUEUE | inqueue | In Queue  
TRANSMITTED | transmitted | Transmitted  
DELIVERED | delivered | Delivered  
###  PrescriptionResponse 
Enum | Value | Label  
---|---|---  
APPROVED | A | Approved  
APPROVED_WITH_CHANGES | C | Approved with changes  
DENIED | D | Denied  
DENIED_PRESCRIPTION_TO_FOLLOW | N | Denied, new prescription to follow  
----- END PAGE https://docs.canvasmedical.com/sdk/data-prescription/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-procedure/
##  Introduction 
The `Procedure` model represents a procedure performed on or ordered for a patient. It is the data model behind the Perform command, is always associated with a Note and a Patient, and has an optional performing provider. Its CPT (or other) codings are available via `codings`.
##  Basic usage 
To get a procedure by identifier, use the `get` method on the `Procedure` model manager:
    ```python
    from canvas_sdk.v1.data.procedure import Procedure
    procedure = Procedure.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    ```
If you have a patient or note object, the procedures for a patient or note can be accessed with the `procedures` attribute:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    from canvas_sdk.v1.data.note import Note
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    procedures = patient.procedures.all()
    note = Note.objects.get(id="89992c23-c298-4118-864a-26cb3e1ae822")
    procedures = note.procedures.all()
    ```
If you have a patient ID, you can get the procedures for the patient with the `for_patient` method on the `Procedure` model manager:
    ```python
    from canvas_sdk.v1.data.procedure import Procedure
    patient_id = "1eed3ea2a8d546a1b681a2a45de1d790"
    procedures = Procedure.objects.for_patient(patient_id)
    ```
##  Codings 
The codings for a procedure can be accessed with the `codings` attribute on a `Procedure` object:
    ```python
    from canvas_sdk.v1.data.procedure import Procedure
    from logger import log
    procedure = Procedure.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    for coding in procedure.codings.all():
        log.info(f"system:  {coding.system}")
        log.info(f"code:    {coding.code}")
        log.info(f"display: {coding.display}")
    ```
##  Filtering 
Procedures can be filtered by any attribute that exists on the model.
Filtering for procedures is done with the `filter` method on the `Procedure` model manager.
###  By attribute 
Specify an attribute with `filter` to filter by that attribute:
    ```python
    from canvas_sdk.v1.data.procedure import Procedure, ProcedureStatus
    procedures = Procedure.objects.filter(status=ProcedureStatus.COMPLETED)
    ```
###  Committed procedures 
The `committed` method returns procedures that have been committed and not entered in error:
    ```python
    from canvas_sdk.v1.data.procedure import Procedure
    committed_procedures = Procedure.objects.committed()
    ```
###  By ValueSet 
See [Value Sets](/sdk/data-value-sets/) for the library of built-in value sets and how to create your own.
Filtering by ValueSet works a little differently. The `find` method on the model manager is used to perform `ValueSet` filtering:
    ```python
    from canvas_sdk.v1.data.procedure import Procedure
    from canvas_sdk.value_set.v2022.procedure import Colonoscopy
    procedures = Procedure.objects.find(Colonoscopy)
    ```
##  Attributes 
###  Procedure 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
originator | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
patient | [Patient](/sdk/data-patient/#patient)  
note | [Note](/sdk/data-note)  
provider | [Staff](/sdk/data-staff/)  
status | ProcedureStatus  
notes | String  
codings | ProcedureCoding[]  
###  ProcedureCoding 
Field Name | Type  
---|---  
dbid | Integer  
system | String  
version | String  
code | String  
display | String  
user_selected | Boolean  
procedure | Procedure  
##  Enumeration types 
###  ProcedureStatus 
Name | Value | Label  
---|---|---  
IN_PROGRESS | 1 | in-progress  
ABORTED | 2 | aborted  
COMPLETED | 3 | completed  
----- END PAGE https://docs.canvasmedical.com/sdk/data-procedure/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-protocol-current/
##  Introduction 
The `ProtocolCurrent` object represents the current state of clinical protocols applied to patients within Canvas. Protocols are typically structured plans or guidelines that outline specific medical interventions, treatments, or care pathways for managing various health conditions. The `ProtocolCurrent` object contains essential information about the protocol's status, associated patient, and relevant clinical details.
##  Basic Usage 
To get a protocol by identifier, use the `get` method on the `ProtocolCurrent` model manager:
    ```python
    from canvas_sdk.v1.data.protocol_current import ProtocolCurrent
    protocol = ProtocolCurrent.objects.get(id="12345678-1234-1234-1234-123456789012")
    ```
##  Filtering 
    ```python
    from canvas_sdk.v1.data.protocol_current import ProtocolCurrent
    protocols = ProtocolCurrent.objects.filter(status="active", patient_id="b80b1cdc2e6a4aca90ccebc02e683f35")
    ```
##  Attributes 
###  ProtocolResult 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
title | String  
narrative | String  
result_identifiers | Array[String]  
types | Array[String]  
protocol_key | String  
plugin_name | String  
status | String  
due_in | DateTime  
days_of_notice | Integer  
snoozed | Boolean  
sources | Array[String]  
recommendations | Array[String]  
top_recommendation_key | String  
next_review | DateTime  
feedback_enabled | Boolean  
plugin_can_be_snoozed | Boolean  
patient_id | UUID  
result_hash | String  
snooze_date | DateTime
----- END PAGE https://docs.canvasmedical.com/sdk/data-protocol-current/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-protocol-override/
##  Introduction 
The `ProtocolOverride` model represents an instance of a protocol being snoozed for a patient.
##  Basic usage 
To get a protocol override by identifier, use the `get` method on the `ProtocolOverride` model manager:
    ```python
    from canvas_sdk.v1.data.protocol_override import ProtocolOverride
    protocol_override = ProtocolOverride.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    ```
If you have a patient object, the protocol overrides for a patient can be accessed with the `protocol_overrides` attribute on a `Patient` object:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    overrides = patient.protocol_overrides.all()
    ```
If you have a patient ID, you can get the protocol overrides for the patient with the `for_patient` method on the `ProtocolOverride` model manager:
    ```python
    from canvas_sdk.v1.data.protocol_override import ProtocolOverride
    patient_id = "1eed3ea2a8d546a1b681a2a45de1d790"
    override = ProtocolOverride.objects.for_patient(patient_id)
    ```
##  Filtering 
Protocol overrides can be filtered by any attribute that exists on the model.
###  By attribute 
Specify an attribute with `filter` to filter by that attribute:
    ```python
    from canvas_sdk.v1.data.protocol_override import ProtocolOverride
    overrides = ProtocolOverride.objects.filter(status="active")
    ```
##  Convenience methods 
The `ProtocolOverride` model manager includes convenience methods for the filters plugins most often apply when working with protocol overrides.
`active` returns the overrides whose `status` is `active`:
    ```python
    from canvas_sdk.v1.data.protocol_override import ProtocolOverride
    active_overrides = ProtocolOverride.objects.active()
    ```
`adjustments` returns the adjustment overrides (`is_adjustment=True`) for a given protocol key, and `snoozes` returns the snooze overrides (`is_snooze=True`) for a given protocol key:
    ```python
    from canvas_sdk.v1.data.protocol_override import ProtocolOverride
    adjustments = ProtocolOverride.objects.adjustments("HCC001v1")
    snoozes = ProtocolOverride.objects.snoozes("HCC001v1")
    ```
Each method returns a queryset, so you can chain them with `for_patient`, `committed`, and with one another. For example, to get the active adjustments for a given patient and protocol key:
    ```python
    from canvas_sdk.v1.data.protocol_override import ProtocolOverride
    adjustments = (
        ProtocolOverride.objects
        .for_patient("1eed3ea2a8d546a1b681a2a45de1d790")
        .committed()
        .active()
        .adjustments("HCC001v1")
    )
    ```
##  Attributes 
###  ProtocolOverride 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
patient | [Patient](/sdk/data-patient/#patient)  
protocol_key | String  
is_adjustment | Boolean  
reference_date | DateTime  
cycle_in_days | Integer  
is_snooze | Boolean  
snooze_date | Date  
snoozed_days | Integer  
snooze_comment | String  
narrative | String  
cycle_quantity | Integer  
cycle_unit | IntervalUnit  
status | Status  
##  Enumeration types 
###  IntervalUnit 
Value | Label  
---|---  
days | days  
months | months  
years | years  
###  Status 
Value | Label  
---|---  
active | active  
inactive | inactive  
----- END PAGE https://docs.canvasmedical.com/sdk/data-protocol-override/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-questionnaire/
##  Introduction 
The `Questionnaire` model represents a structured set of questions intended to guide the collection of answers from end-users.
The `Interview` model represents answers to a structured set of questions represented by a `Questionnaire`.
##  Basic usage 
To get a questionnaire or interview by identifier, use the `get` method on the `Questionnaire` or `Interview` model managers:
    ```python
    from canvas_sdk.v1.data.questionnaire import Interview, Questionnaire
    questionnaire = Questionnaire.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    interview = Interview.objects.get(id="75df6d7f-d58d-443b-9fa0-ce43b4d7b2a0")
    ```
If you have a patient object, the interviews for a patient can be accessed with the `interviews` attribute on a `Patient` object:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    interviews = patient.interviews.all()
    ```
If you have a patient ID, you can get the interviews for the patient with the `for_patient` method on the `Interview` model manager:
    ```python
    from canvas_sdk.v1.data.questionnaire import Interview
    patient_id = "1eed3ea2a8d546a1b681a2a45de1d790"
    interviews = Interview.objects.for_patient(patient_id)
    ```
##  Questionnaire questions 
The questions for a questionnaire can be accessed with the `questions` attribute on an `Questionnaire` object:
    ```python
    from canvas_sdk.v1.data.questionnaire import Questionnaire
    from logger import log
    questionnaire = Questionnaire.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    for question in questionnaire.questions.all():
        log.info(f"system: {question.code_system}")
        log.info(f"code: {question.code}")
        log.info(f"name: {question.name}")
    ```
##  Interview responses 
The interview responses for an interview can be accessed with the `interview_responses` attribute on an `Interview` object:
    ```python
    from canvas_sdk.v1.data.questionnaire import Interview
    from logger import log
    interview = Interview.objects.get(id="75df6d7f-d58d-443b-9fa0-ce43b4d7b2a0")
    for interview_response in interview.interview_responses.all():
        log.info(f"response option: {interview_response.response_option_value}")
    ```
##  Filtering 
Questionnaires and interviews can be filtered by any attribute that exists on the models.
Filtering for questionnaires and interviews is done with the `filter` method on the `Questionnaire` and `Interview` model managers.
###  By attribute 
Specify an attribute with `filter` to filter by that attribute:
    ```python
    from canvas_sdk.v1.data.questionnaire import Interview, Questionnaire
    questionnaires = Questionnaire.objects.filter(name="Tobacco")
    interviews = Interview.objects.filter(progress_status="F")
    ```
###  By ValueSet 
See [Value Sets](/sdk/data-value-sets/) for the library of built-in value sets and how to create your own.
Filtering by ValueSet works a little differently. The `find` method on the model manager is used to perform `ValueSet` filtering:
    ```python
    from canvas_sdk.v1.data.questionnaire import Questionnaire
    from canvas_sdk.value_set.v2022.assessment import TobaccoUseScreening
    questionnaires = Questionnaire.objects.find(TobaccoUseScreening)
    ```
`Interview` also supports `find`, which returns the interviews whose questionnaire has a code in the value set:
    ```python
    from canvas_sdk.v1.data.questionnaire import Interview
    from canvas_sdk.value_set.v2022.assessment import TobaccoUseScreening
    interviews = Interview.objects.find(TobaccoUseScreening)
    ```
For interviews, `find` matches against the related `Questionnaire` through the `questionnaires` relation. Questionnaires store their code system by name (for example, `"LOINC"`) rather than by URL, and `find` handles this for you. It also composes with `for_patient`:
    ```python
    from canvas_sdk.v1.data.questionnaire import Interview
    from canvas_sdk.value_set.v2022.assessment import TobaccoUseScreening
    interviews = (
        Interview.objects
        .for_patient("1eed3ea2a8d546a1b681a2a45de1d790")
        .find(TobaccoUseScreening)
    )
    ```
##  Attributes 
###  ResponseOptionSet 
Field Name | Type  
---|---  
dbid | Integer  
created | DateTime  
modified | DateTime  
status | String  
name | String  
code_system | String  
code | String  
type | String — one of the question types below  
use_in_shx | Boolean  
options | ResponseOption[]  
questions | Question[]  
####  Question types 
`type` holds the code for the kind of question the option set describes. It decides how the question renders in a note and which value an answer carries.
`type` | Question | Answer  
---|---|---  
`TXT` | Free text | Text, on the response's `response_option_value`.  
`INT` | Integer | A whole number.  
`DEC` | Decimal | A decimal number.  
`DATE` | Date | A calendar date, picked from a date picker.  
`SING` | Single select | One ResponseOption.  
`MULT` | Multi select | One or more ResponseOption records.  
`TXT` and `DATE` questions are not scored, so they are skipped when a questionnaire calculates a score. Authoring a questionnaire in a plugin sets this through the question's `responses_type` — see [Questionnaires](/sdk/questionnaires/).
> **Warning:** A `DATE` answer is not readable through the data module yet. It is stored on a date column that `InterviewQuestionResponse` does not expose, so `response_option_value` is empty for a date question. Read it over the FHIR API as a `valueDate` on [QuestionnaireResponse](/api/questionnaireresponse/) in the meantime. 
###  ResponseOption 
Field Name | Type  
---|---  
dbid | Integer  
created | DateTime  
modified | DateTime  
status | String  
name | String  
code | String  
code_description | String  
value | String  
response_option_set | ResponseOptionSet  
ordering | Integer  
interview_responses | InterviewQuestionResponse[]  
###  Question 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
status | String  
name | String  
response_option_set | ResponseOptionSet  
acknowledge_only | Boolean  
show_prologue | Boolean  
code_system | String  
code | String  
interview_responses | InterviewQuestionResponse[]  
###  Questionnaire 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
status | String  
name | String  
expected_completion_time | Float  
can_originate_in_charting | Boolean  
use_case_in_charting | String  
scoring_function_name | String  
scoring_code_system | String  
scoring_code | String  
code_system | String  
code | String  
search_tags | String  
questions | Question[]  
use_in_shx | Boolean  
carry_forward | String  
interview_responses | InterviewQuestionResponse[]  
###  QuestionnaireQuestionMap 
Field Name | Type  
---|---  
dbid | Integer  
created | DateTime  
modified | DateTime  
status | String  
questionnaire | Questionnaire  
question | Question  
###  Interview 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
status | String  
name | String  
language_id | Integer  
use_case_in_charting | String  
patient | [Patient](/sdk/data-patient/#patient)  
note_id | Integer  
appointment_id | Integer  
questionnaires | Questionnaire[]  
progress_status | String  
created | DateTime  
modified | DateTime  
interview_responses | InterviewQuestionResponse[]  
assessment_set | [Assessment](/sdk/data-assessment/#assessment)[]  
###  InterviewQuestionResponse 
Field Name | Type  
---|---  
dbid | Integer  
created | DateTime  
modified | DateTime  
status | String  
interview | Interview  
questionnaire | Questionnaire  
question | Question  
response_option | ResponseOption  
response_option_value | String  
questionnaire_state | String  
interview_state | String  
comment | String  
----- END PAGE https://docs.canvasmedical.com/sdk/data-questionnaire/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-reason-for-visit/
##  Introduction 
This page covers three models:
  - `ReasonForVisit` — a Reason for Visit recorded on a note, and the anchor for the [Reason for Visit](/sdk/commands/#reasonforvisit) command.
  - `ReasonForVisitCoding` — the codings on a recorded Reason for Visit.
  - `ReasonForVisitSettingCoding` — the configured codings an instance offers, used to populate the coding field when a Reason for Visit is recorded.
##  ReasonForVisit 
A `ReasonForVisit` is always associated with a note and a patient. To get one by identifier:
    ```python
    from canvas_sdk.v1.data import ReasonForVisit
    rfv = ReasonForVisit.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    ```
From a patient or a note, use the `reasons_for_visit` attribute:
    ```python
    from canvas_sdk.v1.data import Note, Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    reasons = patient.reasons_for_visit.all()
    note = Note.objects.get(id="89992c23-c298-4118-864a-26cb3e1ae822")
    reasons = note.reasons_for_visit.all()
    ```
###  Reading the narrative 
The text is exposed through the `narrative` property, which returns the free-text value when there is one and otherwise renders the structured `narrative_json`:
    ```python
    from canvas_sdk.v1.data import ReasonForVisit
    rfv = ReasonForVisit.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    text = rfv.narrative
    ```
###  Committed reasons for visit 
The `committed` method returns records that have been committed and not entered in error:
    ```python
    from canvas_sdk.v1.data import ReasonForVisit
    committed = ReasonForVisit.objects.committed()
    ```
###  Codings 
Each `ReasonForVisit` exposes its codings through `codings`:
    ```python
    from canvas_sdk.v1.data import ReasonForVisit
    rfv = ReasonForVisit.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    codings = rfv.codings.all()
    ```
###  Attributes 
####  ReasonForVisit 
Field Name | Type | Description  
---|---|---  
id | UUID | The universally unique identifier for this record.  
dbid | Integer | The database identifier for this record.  
created | DateTime | When the record was created.  
modified | DateTime | When the record was last modified.  
originator | [CanvasUser](/sdk/data-canvasuser) | The user who originated the command.  
committer | [CanvasUser](/sdk/data-canvasuser) | The user who committed the command, if it has been committed.  
entered_in_error | [CanvasUser](/sdk/data-canvasuser) | The user who entered the record in error, if it has been.  
patient | [Patient](/sdk/data-patient/#patient) | The patient the reason for visit was recorded for.  
note | [Note](/sdk/data-note) | The note it was recorded on.  
narrative | String | The reason for visit text.  
codings | _list_ | The `ReasonForVisitCoding` records on this reason for visit.  
####  ReasonForVisitCoding 
Field Name | Type | Description  
---|---|---  
dbid | Integer | The database identifier for this coding record.  
code | String | The code representing the concept.  
display | String | The human-readable display name for the concept.  
system | String | The coding system.  
version | String | The version of the coding system.  
user_selected | Boolean | Whether a user chose this coding directly.  
reason_for_visit | ReasonForVisit | The reason for visit this coding belongs to.  
##  ReasonForVisitSettingCoding 
The `ReasonForVisitSettingCoding` model represents the coding information used to populate the coding field within a Reason For Visit in Canvas.
###  Basic Usage 
To retrieve a specific coding record by its identifier, use the model manager's `get` method:
    ```python
    from canvas_sdk.v1.data import ReasonForVisitSettingCoding
    rfv_coding = ReasonForVisitSettingCoding.objects.get(id="e2b1e1e3-3f52-4a0a-bb3a-123456789abc")
    ```
You can also filter records by attributes. For example, to get all codings from a specific coding system:
    ```python
    from canvas_sdk.v1.data import ReasonForVisitSettingCoding
    codings = ReasonForVisitSettingCoding.objects.filter(system="http://snomed.info/sct")
    ```
###  Attributes 
####  ReasonForVisitSettingCoding 
Field Name | Type | Description  
---|---|---  
id | UUID | The universally unique identifier for this coding record.  
dbid | Integer | The database identifier for this coding record.  
code | String | The code representing the concept.  
display | String | The human-readable display name for the concept.  
system | String | The coding system (e.g., `http://snomed.info/sct`).  
version | String | The version of the coding system.  
duration | Array of Duration | An array of durations (as Python `timedelta` objects) associated with the coding.  
user_selected | Boolean | The active/inactive flag for this reason-for-visit coding: `True` = active, `False` = inactive.  
----- END PAGE https://docs.canvasmedical.com/sdk/data-reason-for-visit/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-referral/
##  Introduction 
The `Referral`, `ReferralReport`, and `ReferralReview` models represent referral results and their reviews.
##  Basic Usage 
To retrieve a `Referral`, `ReferralReport`, or `ReferralReview` by identifier, use the `get` method on the model manager:
    ```python
    from canvas_sdk.v1.data.referral import Referral, ReferralReport, ReferralReview
    referral = Referral.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    referral_report = ReferralReport.objects.get(id="c1a5a35a-4ee2-4a0e-85c0-21739dc8c4a8")
    referral_review = ReferralReview.objects.get(id="b3e6f74c-2a1b-4c8d-9f2e-31842ae7d3b9")
    ```
If you have a patient object, the referrals, reports, and reviews can be accessed with the `referral_set`, `referral_reports`, and `referral_reviews` attributes, respectively on a `Patient` object:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    referrals = patient.referral_set.all()
    reports = patient.referral_reports.all()
    reviews = patient.referral_reviews.all()
    ```
##  Filtering 
Referrals, reports, and reviews can be filtered by any attribute that exists on the models.
Filtering is done with the `filter` method on the `Referral`, `ReferralReport`, and `ReferralReview` model managers.
###  By attribute 
Specify an attribute with `filter` to filter by that attribute:
    ```python
    from canvas_sdk.v1.data.referral import Referral, ReferralReport, ReferralReview
    referrals = Referral.objects.filter(priority="urgent")
    reports = ReferralReport.objects.filter(requires_signature=True)
    reviews = ReferralReview.objects.filter(status="completed")
    ```
###  By ValueSet 
See [Value Sets](/sdk/data-value-sets/) for the library of built-in value sets and how to create your own.
`ReferralReport` supports `ValueSet` filtering through the `find` method on its model manager:
    ```python
    from canvas_sdk.v1.data.referral import ReferralReport
    from canvas_sdk.value_set.v2022.procedure import DialysisServices
    reports = ReferralReport.objects.find(DialysisServices)
    ```
`find` joins through the report's `codings` reverse relation and matches on `(system, code)` pairs from the value set, so a coding must match both the code system and the code to be included.
###  Committed records 
The `committed` method returns `Referral` and `ReferralReview` records that have been committed and not entered in error:
    ```python
    from canvas_sdk.v1.data.referral import Referral, ReferralReview
    committed_referrals = Referral.objects.committed()
    committed_reviews = ReferralReview.objects.committed()
    ```
##  Related Tasks 
To retrieve an Referral's related tasks, use the `get_task_objects` method on the Referral object.
    ```python
    from canvas_sdk.v1.data.referral import Referral
    referral = Referral.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    tasks = referral.get_task_objects().all()
    ```
The `task_list` computed property returns the same related tasks as a `list[Task]`:
    ```python
    from canvas_sdk.v1.data.referral import Referral
    referral = Referral.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    tasks = referral.task_list
    ```
##  The document reference 
`ReferralReport` carries the consult report's specialty, review state and comments, not the file. Canvas stores the file on a [DocumentReference](/sdk/data-document-reference/#the-related-object) pointing back at the report, which is also how it appears in the FHIR API.
To read it, resolve the [ContentType](/sdk/data-content-type/) at runtime from its stable `app_label` and `model` — never hardcode the per-environment `dbid` — and match `object_id` against the report's `dbid`:
    ```python
    from canvas_sdk.v1.data import ContentType, DocumentReference, ReferralReport
    report = ReferralReport.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    content_type = ContentType.objects.filter(app_label="api", model="referralreport").first()
    document = DocumentReference.objects.filter(
        content_type=content_type, object_id=report.dbid
    ).first()
    url = document.document_url if document else None
    ```
> **Info:** `object_id` holds the related record's integer `dbid`, not its UUID `id`. 
##  Attributes 
###  Referral 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
originator | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
patient | [Patient](/sdk/data-patient/#patient)  
note | [Note](/sdk/data-note/#note)  
assessments | [Assessment](/sdk/data-assessment/#assessment)  
service_provider | [ServiceProvider](/sdk/data-serviceprovider/#service-provider)  
clinical_question | String  
priority | String  
include_visit_note | Boolean  
notes | String  
date_referred | DateTime  
internal_comment | String  
forwarded | Boolean  
ignored | Boolean  
internal_task_comment | [TaskComment](/sdk/data-task/#taskcomment)  
task_ids | String  
reports | ReferralReport[]  
###  ReferralReport 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
originator | [CanvasUser](/sdk/data-canvasuser)  
assigned_by | [CanvasUser](/sdk/data-canvasuser)  
review_mode | [DocumentReviewMode](/sdk/data-enumeration-types/#documentreviewmode)  
junked | Boolean  
requires_signature | Boolean  
assigned_date | DateTime  
team_assigned_date | DateTime  
team | [Team](/sdk/data-team/#team)  
patient | [Patient](/sdk/data-patient/#patient)  
referral | Referral  
specialty | String  
comment | String  
priority | Boolean  
original_date | Date  
review | ReferralReview  
codings | ReferralReportCoding[]  
###  ReferralReview 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
originator | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
internal_comment | String  
message_to_patient | String  
status | String  
note | [Note](/sdk/data-note/#note)  
patient | [Patient](/sdk/data-patient/#patient)  
patient_communication_method | String  
reports | ReferralReport[]  
###  ReferralReportCoding 
Field Name | Type  
---|---  
dbid | Integer  
report | ReferralReport  
system | String  
version | String  
code | String  
display | String  
user_selected | Boolean  
value | String  
----- END PAGE https://docs.canvasmedical.com/sdk/data-referral/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-refill-request/
##  Introduction 
The `RefillRequest` model represents an incoming request to refill a patient's medication — for example, a renewal request received electronically from a pharmacy. Each request carries the raw request payload in its `content` attribute, the associated patient and staff member, the medication codings that describe the requested drug (`RefillRequestCoding`), and the prescription(s) written in response.
An incoming `RefillRequest` is routed to a [staff](/sdk/data-staff/#staff) member — the provider expected to respond to it, who becomes the `prescriber` of the responding prescription, not the original requester — and can be marked as `ignored` to drop it from the active refill worklist. Once acted on, the request links to the responding prescription(s) through its `response` attribute, and each [Prescription](/sdk/data-prescription/#prescription) points back to the request through its `refill_request` field. Because the request originates from the pharmacy, and a pharmacy may route it to a provider other than the original prescriber, `staff` is not the requester; it is also nullable, so it may be unset.
##  Basic usage 
To get a refill request by identifier, use the `get` method on the `RefillRequest` model manager:
    ```python
    from canvas_sdk.v1.data.refill_request import RefillRequest
    refill_request = RefillRequest.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    ```
If you have a patient object, the refill requests for a patient can be accessed with the `refill_requests` attribute on a `Patient` object:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    refill_requests = patient.refill_requests.all()
    ```
If you have a patient ID, you can get the refill requests for the patient with the `for_patient` method on the `RefillRequest` model manager:
    ```python
    from canvas_sdk.v1.data.refill_request import RefillRequest
    patient_id = "1eed3ea2a8d546a1b681a2a45de1d790"
    refill_requests = RefillRequest.objects.for_patient(patient_id)
    ```
##  Filtering 
Refill requests can be filtered by any attribute that exists on the model.
Filtering is done with the `filter` method on the `RefillRequest` model manager.
###  By attribute 
Specify an attribute with `filter` to filter by that attribute:
    ```python
    from canvas_sdk.v1.data.refill_request import RefillRequest
    outstanding_requests = RefillRequest.objects.filter(ignored=False)
    ```
The `ignored` attribute is a boolean dismiss flag (default `False`). Marking a request ignored removes it from the active refill worklist and is used to suppress duplicates — for example, a pharmacy re-sending a request. Requests that have already been responded to are excluded from the worklist separately, through their linked `response`. A plugin can filter on `ignored`, but setting it is not available through the data module. The example above returns the active (non-dismissed) requests.
##  Related data 
A refill request's medication codings are available through the `codings` reverse relation, and the prescriptions written in response are available through the `response` reverse relation:
    ```python
    from canvas_sdk.v1.data.refill_request import RefillRequest
    from logger import log
    refill_request = RefillRequest.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    for coding in refill_request.codings.all():
        log.info(f"system:  {coding.system}")
        log.info(f"code:    {coding.code}")
        log.info(f"display: {coding.display}")
    responding_prescriptions = refill_request.response.all()
    ```
The `RefillRequestCoding` entries represent the coding of the requested drug (for example, FDB or RxNorm), with an unstructured fallback whose `display` carries the drug description text when no structured code is available.
##  Message content 
The `message_id` and `content` attributes carry the details of the inbound eRx message.
`message_id` is the eRx (NCPDP SCRIPT / Surescripts) message identifier of the inbound refill-renewal request itself.
`content` is a JSON field holding the parsed inbound NCPDP SCRIPT refill-renewal request payload. It is a free-form, unstructured representation whose exact shape can vary between messages. The information typically available includes:
  - the pharmacy (name, NCPDP ID, phone, address)
  - the prescriber as reported by the sender (name, NPI, SPI, and a sender-supplied identifier)
  - the dispensed and prescribed medication details (drug description, NDC, quantity, days supply, directions, number of refills, substitution allowance, written date, and similar)
  - reference identifiers such as the Rx reference number and the message ID of the original prescription it renews — distinct from this request's own `message_id`
`content` is always a JSON object — it defaults to an empty object (`{}`) when no data was captured — so it is safe to call `.get()` on, but individual keys may be absent. Because the shape is not guaranteed, plugins should access `content` defensively, checking that a key is present before relying on it.
##  Attributes 
###  RefillRequest 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
patient | [Patient](/sdk/data-patient/#patient)  
staff | [Staff](/sdk/data-staff/#staff)  
message_id | String  
ignored | Boolean  
content | JSON  
codings | RefillRequestCoding[]  
response | [Prescription](/sdk/data-prescription/#prescription)[]  
###  RefillRequestCoding 
Field Name | Type  
---|---  
dbid | Integer  
refill_request | RefillRequest  
system | String  
version | String  
code | String  
display | String  
user_selected | Boolean  
----- END PAGE https://docs.canvasmedical.com/sdk/data-refill-request/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-remove-allergy-event/
##  Introduction 
The `RemoveAllergyEvent` model represents a record of an allergy being removed from a patient's allergy list — the anchor for the [Remove Allergy](/sdk/commands/#removeallergy) command.
##  Basic usage 
To get a remove allergy event by identifier, use the `get` method on the `RemoveAllergyEvent` model manager:
    ```python
    from canvas_sdk.v1.data import RemoveAllergyEvent
    removal = RemoveAllergyEvent.objects.get(id="61a1853f-168f-4ed3-80d2-44e5d144bcf3")
    ```
If you have a patient object, the remove allergy events for a patient can be accessed with the `removed_allergies` attribute on a `Patient` object:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    removals = patient.removed_allergies.all()
    ```
The same records are reachable from the note they were recorded on, with the `removed_allergies` attribute on a `Note` object:
    ```python
    from canvas_sdk.v1.data.note import Note
    note = Note.objects.get(id="89992c23-c298-4118-864a-26cb3e1ae822")
    removals = note.removed_allergies.all()
    ```
You can also access the removed allergy with the `allergy` attribute:
    ```python
    from canvas_sdk.v1.data import RemoveAllergyEvent
    removal = RemoveAllergyEvent.objects.get(id="61a1853f-168f-4ed3-80d2-44e5d144bcf3")
    allergy = removal.allergy
    ```
Or for a given allergy, you can access all of its removal events with the `remove_allergy_events` attribute:
    ```python
    from canvas_sdk.v1.data import AllergyIntolerance
    allergy = AllergyIntolerance.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    removals = allergy.remove_allergy_events.all()
    ```
##  Committed records 
The `committed` method returns remove allergy events that have been committed and not entered in error:
    ```python
    from canvas_sdk.v1.data import RemoveAllergyEvent
    committed_removals = RemoveAllergyEvent.objects.committed()
    ```
##  Attributes 
###  RemoveAllergyEvent 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
originator | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
patient | [Patient](/sdk/data-patient/#patient)  
note | [Note](/sdk/data-note)  
allergy | [AllergyIntolerance](/sdk/data-allergy-intolerance)  
rationale | String  
----- END PAGE https://docs.canvasmedical.com/sdk/data-remove-allergy-event/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-resolve-condition-event/
##  Introduction 
The `ResolveConditionEvent` model represents a record of a condition being resolved — the anchor for the [Resolve Condition](/sdk/commands/#resolve-condition) command.
##  Basic usage 
To get a resolve condition event by identifier, use the `get` method on the `ResolveConditionEvent` model manager:
    ```python
    from canvas_sdk.v1.data import ResolveConditionEvent
    resolution = ResolveConditionEvent.objects.get(id="61a1853f-168f-4ed3-80d2-44e5d144bcf3")
    ```
If you have a patient object, the resolve condition events for a patient can be accessed with the `resolved_conditions` attribute on a `Patient` object:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    resolutions = patient.resolved_conditions.all()
    ```
The same records are reachable from the note they were recorded on, with the `resolved_conditions` attribute on a `Note` object:
    ```python
    from canvas_sdk.v1.data.note import Note
    note = Note.objects.get(id="89992c23-c298-4118-864a-26cb3e1ae822")
    resolutions = note.resolved_conditions.all()
    ```
You can also access the resolved condition with the `condition` attribute:
    ```python
    from canvas_sdk.v1.data import ResolveConditionEvent
    resolution = ResolveConditionEvent.objects.get(id="61a1853f-168f-4ed3-80d2-44e5d144bcf3")
    condition = resolution.condition
    ```
Or for a given condition, you can access all of its resolutions with the `resolutions` attribute:
    ```python
    from canvas_sdk.v1.data import Condition
    condition = Condition.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    resolutions = condition.resolutions.all()
    ```
##  Committed records 
The `committed` method returns resolve condition events that have been committed and not entered in error:
    ```python
    from canvas_sdk.v1.data import ResolveConditionEvent
    committed_resolutions = ResolveConditionEvent.objects.committed()
    ```
##  Attributes 
###  ResolveConditionEvent 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
originator | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
patient | [Patient](/sdk/data-patient/#patient)  
note | [Note](/sdk/data-note)  
condition | [Condition](/sdk/data-condition)  
rationale | String  
show_in_condition_list | Boolean  
----- END PAGE https://docs.canvasmedical.com/sdk/data-resolve-condition-event/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-serviceprovider/
##  Introduction 
A `ServiceProvider` is an external provider or organization — someone outside your practice that a patient's care touches. The same record backs every surface in Canvas where an outside provider gets picked:
  - The contact selected as the recipient of a [Refer](/sdk/commands/#refer) command.
  - The imaging center selected on an [Imaging Order](/sdk/commands/#imagingorder) command.
  - An external care team member added to a patient's profile.
  - The recipient of an outbound fax, and the matched sender of an inbound one — Data Integration looks up the sending fax number in the contact directory and links the resulting provider to the incoming document, which is what the `integration_tasks` relation below exposes.
Service providers come from two places. Most are drawn from the shared external contact directory, which is what those surfaces search by default and which you can query yourself with [`GET /contacts/`](/sdk/utils/#searching-for-contacts-and-service-providers). You can also build your own directory: providers created through the [ServiceProvider effect](/sdk/effect-service-provider/) belong to your instance and are flagged with `is_customer_managed`.
Your own providers are not searched automatically. To offer them in one of the surfaces above, handle that surface's search event and return them yourself — see the helpers under Search results below, and [Offering your own providers alongside the directory](/guides/customize-search-results/#offering-your-own-providers-alongside-the-directory) for a worked handler covering all four surfaces.
##  Basic usage 
To retrieve a `ServiceProvider` by identifier, use the `get` method on the model manager:
    ```python
    from canvas_sdk.v1.data.service_provider import ServiceProvider
    service_provider = ServiceProvider.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    ```
To retrieve a service provider from an `ImagingOrder` or a `Referral`
    ```python
    from canvas_sdk.v1.data.imaging import ImagingOrder
    from canvas_sdk.v1.data.referral import Referral
    imaging_order = ImagingOrder.objects.get(id="9d2e0f58-338b-11ec-8d3d-0242ac130003")
    imaging_order_service_provider = imaging_order.imaging_center
    referral = Referral.objects.get(id="9d2e0f58-338b-11ec-8d3d-0242ac130004")
    referral_service_provider = referral.service_provider
    ```
To show a `ServiceProvider` full name or full name with specialty use the properties `full_name` or `full_name_and_specialty`
    ```python
    from canvas_sdk.v1.data.service_provider import ServiceProvider
    service_provider = ServiceProvider.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    full_name = service_provider.full_name
    full_name_and_specialty = service_provider.full_name_and_specialty
    ```
##  Service Provider 
###  Fields 
Name | Type | Description  
---|---|---  
id | UUID | Unique identifier  
dbid | Integer | Internal database identifier  
first_name | String | Provider name, or the organization name  
last_name | String | Empty for organizations  
business_fax | String |   
business_phone | String |   
business_address | String |   
specialty | String | Free text  
practice_name | String |   
notes | String |   
is_active | Boolean | `False` once deactivated; the provider is kept, not deleted  
npi | String | 10 digits  
direct_address | String | Direct address  
is_customer_managed | Boolean | `True` for providers created through the SDK — see below  
science_contact_id | Integer | The shared directory contact this provider came from, or `None` if it came from none. Not a reliable provenance signal on its own — providers that predate this tracking have no value — so use `is_customer_managed` to identify a customer's own providers.  
imaging_orders | QuerySet[[ImagingOrder](/sdk/data-imaging/#imagingorder)] | Imaging orders sent to this provider  
referrals | QuerySet[[Referral](/sdk/data-referral/#referral)] | Referrals sent to this provider  
integration_tasks | QuerySet[[IntegrationTask](/sdk/data-integration-task/#integrationtask)] | Integration tasks associated with this provider  
##  Customer-managed providers 
`is_customer_managed` is `True` for providers created with the [Service Provider effects](/sdk/effect-service-provider/), and `False` for everything else.
    ```python
    from canvas_sdk.v1.data.service_provider import ServiceProvider
    ServiceProvider.objects.filter(is_customer_managed=True, is_active=True)
    ```
This is also how you find an existing customer-managed provider — to get its `id` — before updating or deactivating it with the [Service Provider effects](/sdk/effect-service-provider/).
##  Search results 
Two helpers shape a provider for the provider-search surfaces, so you do not have to build the payloads yourself. Both take an optional list of annotations, shown next to the result.
Method | Use it for  
---|---  
`as_search_result(annotations=None)` | A command's provider search — `Refer to` on Refer, `Imaging center` on Imaging Order  
`as_search_contact(annotations=None)` | The fax recipient and external care team dropdowns  
Both identify the provider, so selecting one attaches to that exact record.
The two helpers return different shapes, and the key casing below matches the serialized payload exactly:
  - `as_search_result(annotations=None)` returns `text`, `value`, `description`, and `annotations` at the top level, plus an `extra.contact` object containing `service_provider_id`, `science_contact_id`, `firstName`, `lastName`, `businessFax`, `businessPhone`, `businessAddress`, `specialty`, `practiceName`, and `notes`.
  - `as_search_contact(annotations=None)` returns a flat object: `id`, `serviceProviderId`, `firstName`, `lastName`, `practiceName`, `specialty`, `businessAddress`, `businessPhone`, `businessFax`, and `annotations`.
    ```python
    import json
    from canvas_sdk.effects import Effect, EffectType
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers.base import BaseHandler
    from canvas_sdk.v1.data.service_provider import ServiceProvider
    class OwnDirectoryFirst(BaseHandler):
        RESPONDS_TO = [EventType.Name(EventType.FAX__RECIPIENT__PRE_SEARCH), EventType.Name(EventType.PATIENT_PROFILE__EXTERNAL_CARE_TEAM__PRE_SEARCH)]
        def compute(self):
            term = self.event.context.get("search_term", "").strip()
            if not term:
                return []
            providers = ServiceProvider.objects.filter(
                is_customer_managed=True, first_name__icontains=term
            )
            if not providers:
                return []
            return [
                Effect(
                    type=EffectType.AUTOCOMPLETE_SEARCH_RESULTS,
                    payload=json.dumps(
                        [
                            provider.as_search_contact(
                                [] if provider.is_active else ["Inactive"]
                            )
                            for provider in providers
                        ]
                    ),
                )
            ]
    ```
Both surfaces reply with the same `AUTOCOMPLETE_SEARCH_RESULTS` effect. Only the event you subscribe to and the helper you call differ.
What you return means different things on each surface:
  - **Contact dropdowns, pre-search** — results replace the search; returning nothing runs the normal search instead.
  - **Command searches, post-search** — an empty list clears the results, so return no effect at all when you have nothing to add.
----- END PAGE https://docs.canvasmedical.com/sdk/data-serviceprovider/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-snapshot/
#  Snapshot Models 
The `Snapshot` and `SnapshotImage` models represent images captured via the Canvas iOS application or uploaded directly through the coverages modal. A `Snapshot` groups related images together, while each `SnapshotImage` represents an individual image with presigned URL support for secure access.
Snapshots are primarily used for storing coverage card images. You can navigate from a `Coverage` to its `Snapshot` via the `snapshot` field, and from a `Snapshot` back to its `Coverage` via the `coverage` reverse relation.
##  Basic Usage 
    ```python
    from canvas_sdk.v1.data import Snapshot, SnapshotImage
    # Get all snapshots
    snapshots = Snapshot.objects.all()
    # Get a specific snapshot
    snapshot = Snapshot.objects.get(dbid=42)
    # Get images for a snapshot
    images = snapshot.images.all()
    # Get all snapshot images
    all_images = SnapshotImage.objects.all()
    # Get all snapshot images for a specific coverage
    from canvas_sdk.v1.data.coverage import Coverage
    coverage = Coverage.objects.get(id="a74592ae8a6c4d0ebe0799d3fb3713d1")
    if coverage.snapshot:
        images = coverage.snapshot.images.all()
        for image in images:
            print(image.image_url)
    ```
##  Accessing Image Files 
The `image_url` property on `SnapshotImage` returns a presigned S3 URL for securely accessing the image file.
    ```python
    from canvas_sdk.v1.data import SnapshotImage
    image = SnapshotImage.objects.exclude(image="").first()
    # Returns a presigned S3 URL (valid for 1 hour)
    url = image.image_url
    ```
##  Attributes 
###  Snapshot 
Field Name | Type  
---|---  
dbid | Integer  
created | DateTime  
modified | DateTime  
originator | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
title | String  
description | String  
coverage | [Coverage](/sdk/data-coverage/#coverage)  
images | SnapshotImage[]  
###  SnapshotImage 
Field Name | Type  
---|---  
dbid | Integer  
created | DateTime  
modified | DateTime  
snapshot | Snapshot  
image | String  
title | String  
instruction | String  
tag | String  
image_url | String (property) — presigned S3 URL
----- END PAGE https://docs.canvasmedical.com/sdk/data-snapshot/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-specialty-report-template/
##  Introduction 
The `SpecialtyReportTemplate`, `SpecialtyReportTemplateField`, and `SpecialtyReportTemplateFieldOption` models represent the templates used for specialty and referral reports. Templates define the structure of a specialty report, including what fields need to be filled in and what options are available for each field. Each template can be associated with a medical specialty via taxonomy codes.
##  Basic Usage 
To retrieve a `SpecialtyReportTemplate` by identifier, use the `get` method on the model manager:
    ```python
    from canvas_sdk.v1.data.specialty_report_template import SpecialtyReportTemplate
    template = SpecialtyReportTemplate.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    ```
To access the fields defined in a template:
    ```python
    from canvas_sdk.v1.data.specialty_report_template import SpecialtyReportTemplate
    template = SpecialtyReportTemplate.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    fields = template.fields.all()
    ```
##  Filtering 
Templates can be filtered by any attribute on the models.
###  By active status 
    ```python
    from canvas_sdk.v1.data.specialty_report_template import SpecialtyReportTemplate
    active_templates = SpecialtyReportTemplate.objects.active()
    ```
###  By type 
    ```python
    from canvas_sdk.v1.data.specialty_report_template import SpecialtyReportTemplate
    # Get custom (user-created) templates
    custom = SpecialtyReportTemplate.objects.custom()
    # Get built-in (system) templates
    builtin = SpecialtyReportTemplate.objects.builtin()
    ```
###  By specialty 
    ```python
    from canvas_sdk.v1.data.specialty_report_template import SpecialtyReportTemplate
    # Filter by specialty taxonomy code
    cardiology = SpecialtyReportTemplate.objects.by_specialty("207RC0000X")
    ```
###  By search 
    ```python
    from canvas_sdk.v1.data.specialty_report_template import SpecialtyReportTemplate
    results = SpecialtyReportTemplate.objects.search("cardiology")
    ```
##  Attributes 
###  SpecialtyReportTemplate 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
name | String  
code | String  
code_system | String  
search_keywords | String  
active | Boolean  
custom | Boolean  
search_as | String  
specialty_name | String  
specialty_code | String  
specialty_code_system | String  
fields | SpecialtyReportTemplateField[]  
###  SpecialtyReportTemplateField 
Field Name | Type  
---|---  
dbid | Integer  
report_template | SpecialtyReportTemplate  
sequence | Integer  
code | String  
code_system | String  
label | String  
units | String  
type | String  
required | Boolean  
options | SpecialtyReportTemplateFieldOption[]  
###  SpecialtyReportTemplateFieldOption 
Field Name | Type  
---|---  
dbid | Integer  
field | SpecialtyReportTemplateField  
label | String  
key | String  
----- END PAGE https://docs.canvasmedical.com/sdk/data-specialty-report-template/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-staff/
##  Introduction 
The `Staff` model represents a staff member in a Canvas instance.
To get a `Staff` object by it's identifier, use the `get` method:
    ```python
    from canvas_sdk.v1.data.staff import Staff
    staff = Staff.objects.get(id="4150cd20de8a470aa570a852859ac87e")
    ```
`Staff` objects are commonly used in related models, for example the `Task` model. To see all of a staff member's assigned or created tasks, the following code can be used:
    ```python
    from canvas_sdk.v1.data.staff import Staff
    staff = Staff.objects.get(id="4150cd20de8a470aa570a852859ac87e")
    staff.assignee_tasks.all()
    # <QuerySet [<Task: Task object (3)>]>
    staff.creator_tasks.all()
    # <QuerySet [<Task: Task object (7)>]>
    ```
To show a Staff member's contact points (email, phone, etc.), the `telecom` attribute can be used. For example:
    ```python
    from canvas_sdk.v1.data.staff import Staff
    staff = Staff.objects.get(id="4150cd20de8a470aa570a852859ac87e")
    [(t.system, t.value,) for t in staff.telecom.all()]
    # [('phone', '8005551416'), ('email', 'support@canvasmedical.com')]
    ```
To show a `Staff` full name, credentialed name, the topmost clinical role or top role abbreviation use the properties `full_name`, `credentialed_name`, `top_clinical_role` or `top_role_abbreviation`.
    ```python
    from canvas_sdk.v1.data.staff import Staff
    staff = Staff.objects.get(id="4150cd20de8a470aa570a852859ac87e")
    staff.full_name
    # Larry Weed
    staff.credentialed_name
    # Larry Weed MD
    staff.top_clinical_role.name
    # Physician
    staff.top_role_abbreviation
    # MD
    ```
When a staff member holds more than one role, `top_clinical_role` looks only at roles in a clinical domain — those whose `domain` is `CLINICAL` or `HYBRID` — and returns the one with the highest `domain_privilege_level`. Administrative roles are never selected, even if they carry a higher privilege level. If the staff member has no clinical or hybrid roles, both `top_clinical_role` and `top_role_abbreviation` are `None`. Because `credentialed_name` appends `top_role_abbreviation`, it reflects the same highest-privilege clinical role.
To get `Staff` licenses.
    ```python
    from canvas_sdk.v1.data.staff import Staff
    staff = Staff.objects.get(id="4150cd20de8a470aa570a852859ac87e")
    staff.licenses.all()
    # <QuerySet [<StaffLicense: CA License for Larry Weed>]>
    ```
##  Accessing the staff signature 
The `signature_url` property returns a presigned S3 URL for securely accessing the staff member's signature file, when one is on file. If no signature has been uploaded, the property returns `None`.
    ```python
    from canvas_sdk.v1.data.staff import Staff
    staff = Staff.objects.get(id="4150cd20de8a470aa570a852859ac87e")
    # Returns a presigned S3 URL (valid for 1 hour) or None
    url = staff.signature_url
    ```
##  Attributes 
###  Staff 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
prefix | String  
suffix | String  
first_name | String  
middle_name | String  
last_name | String  
maiden_name | String  
nickname | String  
previous_names | JSON  
birth_date | Date  
sex_at_birth | [PersonSex](/sdk/data-enumeration-types/#personsex)  
sexual_orientation_term | String  
sexual_orientation_code | String  
gender_identity_term | String  
gender_identity_code | String  
preferred_pronouns | String  
biological_race_codes | Array[String]  
biological_race_terms | Array[String]  
cultural_ethnicity_codes | Array[String]  
cultural_ethnicity_terms | Array[String]  
last_known_timezone | TimeZone  
active | Boolean  
primary_practice_location | [PracticeLocation](/sdk/data-practicelocation/)  
npi_number | String  
nadean_number | String  
group_npi_number | String  
bill_through_organization | Boolean  
tax_id | String  
tax_id_type | [TaxIDType](/sdk/data-enumeration-types/#taxidtype)  
spi_number | String  
personal_meeting_room_link | URL  
language | Language  
language_secondary | Language  
schedule_column_ordering | Integer  
state | JSON  
user | [CanvasUser](/sdk/data-canvasuser)  
signature | String  
supervising_team | Staff[]  
default_supervising_provider | Staff  
notes | Note[]  
supervised_notes | Note[]  
creator_tasks | [Task](/sdk/data-task/#task)[]  
assignee_tasks | [Task](/sdk/data-task/#task)[]  
comments | [TaskComment](/sdk/data-task/#taskcomment)[]  
care_team_memberships | [CareTeamMembership](/sdk/data-care-team/#careteammembership)[]  
teams | [Team](/sdk/data-team/#team)[]  
telecom | StaffContactPoint[]  
external_identifiers | StaffExternalIdentifier[]  
metadata | StaffMetadata[]  
addresses | StaffAddress[]  
photos | StaffPhoto[]  
roles | StaffRole[]  
licenses | StaffLicense[]  
letters | [Letter](/sdk/data-letter/#letter)[]  
imaging_orders | [ImagingOrder](/sdk/data-imaging/#imagingorder)[]  
immunizations_given | [Immunization](/sdk/data-immunization/#immunization)[]  
supervising_prescriptions | [Prescription](/sdk/data-prescription/#prescription)[]  
refill_requests | [RefillRequest](/sdk/data-refill-request/#refillrequest)[]  
default_patients | [Patient](/sdk/data-patient/#patient)[]  
medication_history_responses | [MedicationHistoryResponse](/sdk/data-medication-history/#medicationhistoryresponse)[]  
transmissions_delivered | [MessageTransmission](/sdk/data-message/#messagetransmission)[]  
integration_task_reviews | [IntegrationTaskReview](/sdk/data-integration-task/#integrationtaskreview)[]  
assignee_note_tasks | [NoteTask](/sdk/data-task/#notetask)[]  
appointment_set | [Appointment](/sdk/data-appointment/#appointment)[]  
prescription_set | [Prescription](/sdk/data-prescription/#prescription)[]  
note_set | [Note](/sdk/data-note/#note)[]  
prescription_change_requests | [PrescriptionChangeRequest](/sdk/data-prescription-change-request/#prescriptionchangerequest)[]  
###  StaffContactPoint 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
system | [ContactPointSystem](/sdk/data-enumeration-types/#contactpointsystem)  
value | String  
use | String  
use_notes | String  
rank | Integer  
state | [ContactPointState](/sdk/data-enumeration-types/#contactpointstate)  
staff | Staff  
###  StaffAddress 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
line1 | String  
line2 | String  
city | String  
district | String  
state_code | String  
postal_code | String  
use | [AddressUse](/sdk/data-enumeration-types/#addressuse)  
type | [AddressType](/sdk/data-enumeration-types/#addresstype)  
longitude | Float  
latitude | Float  
start | Date  
end | Date  
country | String  
state | String  
staff | Staff  
###  StaffLicense 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
staff | Staff  
issuing_authority_long_name | String  
issuing_authority_url | URL  
license_or_certification_identifier | String  
issuance_date | Date  
expiration_date | Date  
license_type | LicenseType  
primary | Boolean  
state | String  
###  StaffPhoto 
Field Name | Type  
---|---  
dbid | Integer  
created | DateTime  
modified | DateTime  
staff | Staff  
url | String  
title | String  
###  StaffRole 
Field Name | Type  
---|---  
dbid | Integer  
staff | Staff  
internal_code | String  
public_abbreviation | String  
domain | RoleDomain  
name | String  
domain_privilege_level | Integer  
permissions | JSON  
role_type | RoleType  
###  StaffExternalIdentifier 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
staff | Staff  
use | String  
identifier_type | String  
system | String  
value | String  
issued_date | Date  
expiration_date | Date  
    ```python
    from canvas_sdk.v1.data.staff import Staff
    from logger import log
    staff_id = "4150cd20de8a470aa570a852859ac87e"
    staff = Staff.objects.get(id=staff_id)
    for identifier in staff.external_identifiers.all():
        log.info(f"Staff external identifier: {identifier.system}, {identifier.value}")
        # https://www.example.com - employee-001
    ```
###  StaffMetadata 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
staff | Staff  
key | String  
value | String  
    ```python
    from canvas_sdk.v1.data.staff import Staff
    from logger import log
    staff_id = "4150cd20de8a470aa570a852859ac87e"
    staff = Staff.objects.get(id=staff_id)
    for metadata in staff.metadata.all():
        log.info(f"{metadata.key}={metadata.value}")
    ```
`StaffMetadata` is a free-form key/value store on a staff member, mirroring `PatientMetadata`. The `(staff, key)` pair is unique, so a given key has at most one value per staff member; use the [`StaffMetadata` effect](/sdk/effect-staff-metadata/) to upsert it from a plugin.
##  Enumeration types 
###  License Type 
Value | Description  
---|---  
CLIA | CLIA  
DEA | DEA  
PTAN | PTAN  
STATE_LICENSE | State License  
TAXONOMY | Taxonomy  
SPI | SPI  
OTHER | Other  
###  Role Domain 
Value | Abbreviation | Description  
---|---|---  
CLINICAL | CLI | Clinical  
ADMINISTRATIVE | ADM | Administrative  
HYBRID | HYB | Hybrid  
###  Role Type 
Value | Description  
---|---  
NON_LICENSED | Non-Licensed  
LICENSED | Licensed  
PROVIDER | Provider  
##  Computed Properties 
  - `full_name`: The staff member's first and last name (for example, `Larry Weed`).
  - `credentialed_name`: The staff member's full name suffixed with their topmost credential abbreviation (for example, `Larry Weed MD`).
  - `top_clinical_role`: The staff member's highest-ranking clinical StaffRole, selected by privilege level when they hold more than one, or `None` if they have no clinical role.
  - `top_role_abbreviation`: The public credential abbreviation of the `top_clinical_role` (for example, `MD`), or `None` if there is no clinical role.
  - `photo_url`: The URL of the staff member's photo, if available, or a placeholder image URL.
  - `signature_url`: A presigned S3 URL for the staff member's signature file (valid for 1 hour), or `None` if no signature is on file.
----- END PAGE https://docs.canvasmedical.com/sdk/data-staff/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-stop-medication-event/
##  Introduction 
The `StopMedicationEvent` model represents a record of a Stop Medication Event, when a medication is removed from a patient's medication list.
##  Basic usage 
To get a stop medication event by identifier, use the `get` method on the `StopMedicationEvent` model manager:
    ```python
    from canvas_sdk.v1.data import StopMedicationEvent
    stopped_medication = StopMedicationEvent.objects.get(id="61a1853f-168f-4ed3-80d2-44e5d144bcf3")
    ```
If you have a patient object, the stop medication events for a patient can be accessed with the `stopped_medications` attribute on a `Patient` object:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    stopped_medications = patient.stopped_medications.all()
    ```
You can also access the referenced medication with the `medication` attribute:
    ```python
    from canvas_sdk.v1.data import StopMedicationEvent
    stopped_medication = StopMedicationEvent.objects.get(id="61a1853f-168f-4ed3-80d2-44e5d144bcf3")
    medication = stopped_medication.medication
    ```
Or for a given medication, you can access all stop events:
    ```python
    from canvas_sdk.v1.data import Medication
    medication = Medication.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    stopped_medication_events = medication.stopmedicationevent_set.all()
    ```
##  Committed records 
The `committed` method returns stop medication events that have been committed and not entered in error:
    ```python
    from canvas_sdk.v1.data import StopMedicationEvent
    committed_stop_medication_events = StopMedicationEvent.objects.committed()
    ```
##  Attributes 
###  StopMedicationEvent 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
patient | [Patient](/sdk/data-patient/#patient)  
note | [Note](/sdk/data-note)  
medication | [Medication](/sdk/data-medication)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
originator | [CanvasUser](/sdk/data-canvasuser)  
created | DateTime  
modified | DateTime  
rationale | String  
----- END PAGE https://docs.canvasmedical.com/sdk/data-stop-medication-event/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-task/
##  Introduction 
A `Task` represents a to-do item to be addressed. Tasks can be assigned to individual staff members and can also have associated comments and labels.
##  Basic usage 
To get a task by it's identifier, use the `get` method on the `Task` model manager:
    ```python
    from canvas_sdk.v1.data.task import Task
    task = Task.objects.get(id="7895e1db-f8de-4660-a0a3-9e5b43a475c6")
    ```
From a `Patient` object, tasks for the patient can be accessed with the `tasks` attribute:
    ```python
    import arrow
    from canvas_sdk.v1.data.patient import Patient
    from canvas_sdk.v1.data.task import TaskStatus
    patient = Patient.objects.get(id="36950971cb3e4174ad8b9d365abfd6d0")
    # All tasks for the patient
    tasks_for_patient = patient.tasks.all()
    # Tasks for the patient that are overdue
    tasks_for_patient_overdue = patient.tasks.filter(due__lte=arrow.utcnow().datetime, status=TaskStatus.OPEN)
    ```
`Task` objects are also able to have associated `TaskLabel` objects.
    ```python
    from canvas_sdk.v1.data.task import Task
    task = Task.objects.get(id="7895e1db-f8de-4660-a0a3-9e5b43a475c6")
    [(label.name, label.color,) for label in task.labels.all()]
    # [('Emergent', 'red')]
    ```
`Staff` members are able to leave comments on tasks. These are stored as associated `TaskComment` objects. For example:
    ```python
    from canvas_sdk.v1.data.task import Task
    task = Task.objects.get(id="7895e1db-f8de-4660-a0a3-9e5b43a475c6")
    [(comment.creator, comment.body,) for comment in task.comments.all()]
    # [(<Staff: Sam Jones>, "Please call patient.")]
    ```
###  Note Tasks and Initial Comments 
A `NoteTask` represents the link between a Task command and the `Task` it generates. When a task is created via a Task command, a `NoteTask` record is created that stores the original values entered in the command. Of importance is the **initial comment** that is provided during task creation in the `internal_comment` field.
This is important because `task.comments.all()` only returns manual comments added after the task is created through the interface—it does not include the original comment entered during task creation. To access that initial comment, you need to use the `NoteTask` model.
To get a note task by its identifier:
    ```python
    from canvas_sdk.v1.data.task import NoteTask
    note_task = NoteTask.objects.get(id="a1b2c3d4-e5f6-7890-abcd-ef1234567890")
    print(f"Initial comment: {note_task.internal_comment}")
    ```
From a `Task` object, you can access the associated `NoteTask` to retrieve the initial comment:
    ```python
    from canvas_sdk.v1.data.task import Task
    task = Task.objects.get(id="7895e1db-f8de-4660-a0a3-9e5b43a475c6")
    # Access the NoteTask to get the initial comment
    note_task = task.note_tasks.first()
    if note_task:
        print(f"Initial comment: {note_task.internal_comment}")
        print(f"Original title: {note_task.original_title}")
        print(f"Original assignee: {note_task.original_assignee}")
    ```
Common workflow pattern: handling a TASK_CREATED event and accessing the initial comment:
    ```python
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.v1.data.task import Task
    class TaskCreatedHandler(BaseHandler):
        RESPONDS_TO = [EventType.Name(EventType.TASK_CREATED)]
        def compute(self):
            task_id = self.target
            task = Task.objects.get(id=task_id)
            # Get the initial comment from the NoteTask
            note_task = task.note_tasks.first()
            if note_task:
                initial_comment = note_task.internal_comment
                # Use the initial comment for your logic
                self.log(f"Task created with initial comment: {initial_comment}")
    ```
From a `Note` object, note tasks can be accessed with the `note_tasks` attribute:
    ```python
    from canvas_sdk.v1.data.note import Note
    note = Note.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    note_tasks = note.note_tasks.all()
    for note_task in note_tasks:
        print(f"Task: {note_task.original_title}")
        print(f"Initial comment: {note_task.internal_comment}")
    ```
##  Committed note tasks 
The `committed` method on the `NoteTask` model manager returns note tasks whose underlying Task command has been committed and not entered in error:
    ```python
    from canvas_sdk.v1.data.task import NoteTask
    committed_note_tasks = NoteTask.objects.committed()
    ```
##  Attributes 
###  Task 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
creator | [Staff](/sdk/data-staff/#staff)  
assignee | [Staff](/sdk/data-staff/#staff)  
patient | [Patient](/sdk/data-patient/#patient)  
team | [Team](/sdk/data-team/)  
task_type | TaskType  
tag | String  
title | String  
due | DateTime  
due_event | EventType  
status | TaskStatus  
priority | TaskPriority  
comments | TaskComment[]  
labels | TaskLabel[]  
metadata | TaskMetadata[]  
note_tasks | NoteTask[]  
###  NoteTask 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
originator | [CanvasUser](/sdk/data-canvasuser/)  
committer | [CanvasUser](/sdk/data-canvasuser/)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser/)  
note | [Note](/sdk/data-note/#note)  
task | Task  
patient | [Patient](/sdk/data-patient/#patient)  
original_title | String  
original_assignee | [Staff](/sdk/data-staff/#staff)  
original_team | [Team](/sdk/data-team/)  
original_role | [CareTeamRole](/sdk/data-care-team/#careteamrole)  
original_due | DateTime  
internal_comment | String  
###  TaskComment 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
creator | [Staff](/sdk/data-staff/#staff)  
task | [Task](/sdk/data-task/#task)  
body | String  
referral | [Referral](/sdk/data-referral/)  
###  TaskLabel 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
tasks | M2M  
position | Integer  
color | [ColorEnum](/sdk/data-enumeration-types/#colorenum)  
task_association | [Origin](/sdk/data-enumeration-types/#origin)  
name | String  
active | Boolean  
modules | TaskLabelModule  
claims | [Claim](/sdk/data-claim)[]  
appointments | [Appointment](/sdk/data-appointment/)[]  
###  TaskMetadata 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
task | Task  
key | String  
value | String  
    ```python
    from canvas_sdk.v1.data.task import Task
    from logger import log
    task_id = "7895e1db-f8de-4660-a0a3-9e5b43a475c6"
    task = Task.objects.get(id=task_id)
    task_metadata = task.metadata.all()
    for metadata in task_metadata:
       log.info(f"Task metadata: {metadata.key}, {metadata.value}") # external_system_id - EXT-12345
    ```
##  Enumeration types 
###  TaskType 
Value | Label  
---|---  
Task | Task  
Reminder | Reminder  
###  EventType 
Value | Label  
---|---  
Chart Open | Chart Open  
###  TaskStatus 
Value | Label  
---|---  
COMPLETED | Completed  
CLOSED | Closed  
OPEN | Open  
###  TaskPriority 
Value | Label  
---|---  
STAT | STAT  
URGENT | Urgent  
ROUTINE | Routine  
###  TaskLabelModule 
Value | Label  
---|---  
claims | Claims  
tasks | Tasks  
----- END PAGE https://docs.canvasmedical.com/sdk/data-task/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-team/
##  Introduction 
The `Team` model represents a team of staff members in a Canvas instance.
##  Basic usage 
To get an team by identifier, use the `get` method on the `Team` model manager:
    ```python
    from canvas_sdk.v1.data.team import Team
    team = Team.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    ```
If you have a staff object, the teams that a staff is a member of can be accessed with the `teams` attribute on a `Staff` object:
    ```python
    from canvas_sdk.v1.data.staff import Staff
    staff = Staff.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    teams = staff.teams.all()
    ```
##  Team Members 
The members of a team can be access with the `members` attribute on a `Team` object:
    ```python
    from canvas_sdk.v1.data.team import Team
    from logger import log
    team = Team.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    for member in team.members.all():
        log.info(f"first_name: {member.first_name}")
        log.info(f"last_name: {member.last_name}")
    ```
##  Reconciling with FHIR 
A team's `group_id` is the same identifier used to represent the team in the [FHIR Group endpoint](/api/group/). Use it to cross-reference a `Team` between the SDK and FHIR.
Given a `Team`, you can use its `group_id` with the [Canvas FHIR client](/sdk/clients-canvas-fhir/) to fetch the corresponding FHIR `Group` payload:
    ```python
    from canvas_sdk.clients.canvas_fhir import CanvasFhir
    from canvas_sdk.v1.data.team import Team
    team = Team.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    # Declare these secrets in the CANVAS_MANIFEST.json and set the values on the
    # plugin configuration page.
    client = CanvasFhir(
        self.secrets["CANVAS_FHIR_CLIENT_ID"],
        self.secrets["CANVAS_FHIR_CLIENT_SECRET"],
    )
    # Use the team's group_id to read the corresponding FHIR Group resource.
    group = client.read("Group", str(team.group_id))
    ```
##  Filtering 
Teams can be filtered by any attribute that exists on the model.
Filtering for teams is done with the `filter` method on the `Team` model manager.
###  By attribute 
Specify an attribute with `filter` to filter by that attribute:
    ```python
    from canvas_sdk.v1.data.team import Team
    teams = Team.objects.filter(created__gt="2025-01-01")
    ```
##  Attributes 
###  Team 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
name | String  
responsibilities | Array[TeamResponsibility]  
members | [Staff](/sdk/data-staff/#staff)[]  
group_id | UUID  
telecom | TeamContactPoint[]  
tasks | [Task](/sdk/data-task/#task)[]  
document_references | [DocumentReference](/sdk/data-document-reference/#documentreference)[]  
integration_task_team_reviews | [IntegrationTaskReview](/sdk/data-integration-task/#integrationtaskreview)[]  
uncategorizedclinicaldocument_set | [UncategorizedClinicalDocument](/sdk/data-uncategorized-clinical-document/#uncategorizedclinicaldocument)[]  
referralreport_set | [ReferralReport](/sdk/data-referral/#referralreport)[]  
###  TeamContactPoint 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
system | [ContactPointSystem](/sdk/data-enumeration-types/#contactpointsystem)  
value | String  
use | [ContactPointUse](/sdk/data-enumeration-types/#contactpointuse)  
use_notes | String  
rank | Integer  
state | [ContactPointState](/sdk/data-enumeration-types/#contactpointstate)  
team | Team  
##  Enumeration types 
###  TeamResponsibility 
Field Name | Type  
---|---  
COLLECT_SPECIMENS_FROM_PATIENT | Collect specimens from a patient  
COMMUNICATE_DIAGNOSTIC_RESULTS_TO_PATIENT | Communicate diagnostic results to patient  
COORDINATE_REFERRALS_FOR_PATIENT | Coordinate referrals for a patient  
PROCESS_REFILL_REQUESTS | Process refill requests from a pharmacy  
PROCESS_CHANGE_REQUESTS | Process change requests from a pharmacy  
SCHEDULE_LAB_VISITS_FOR_PATIENT | Schedule lab visits for a patient  
POPULATION_HEALTH_CAMPAIGN_OUTREACH | Population health campaign outreach  
COLLECT_PATIENT_PAYMENTS | Collect patient payments  
COMPLETE_OPEN_LAB_ORDERS | Complete open lab orders  
REVIEW_ERA_POSTING_EXCEPTIONS | Review electronic remittance posting exceptions  
REVIEW_COVERAGES | Review incomplete patient coverages  
----- END PAGE https://docs.canvasmedical.com/sdk/data-team/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-uncategorized-clinical-document/
##  Introduction 
The `UncategorizedClinicalDocument` and `UncategorizedClinicalDocumentReview` models represent uncategorized clinical documents and their reviews.
##  Basic Usage 
    ```python
    from canvas_sdk.v1.data import UncategorizedClinicalDocument, UncategorizedClinicalDocumentReview
    document = UncategorizedClinicalDocument.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    review = UncategorizedClinicalDocumentReview.objects.get(id="c1a5a35a-4ee2-4a0e-85c0-21739dc8c4a8")
    ```
##  Filtering 
Uncategorized clinical documents and reviews can be filtered by any attribute that exists on the models.
###  By review mode 
Filter documents by their review mode:
    ```python
    from canvas_sdk.v1.data import UncategorizedClinicalDocument
    from canvas_sdk.commands.commands.review import ReviewMode
    documents_to_review = UncategorizedClinicalDocument.objects.filter(review_mode=ReviewMode.REVIEW_REQUIRED)
    ```
###  Unreviewed documents 
To get uncategorized documents that have not been reviewed yet and require a review:
    ```python
    from canvas_sdk.v1.data import UncategorizedClinicalDocument
    from canvas_sdk.commands.commands.review import ReviewMode
    from django.db.models import Q
    unreviewed_documents = UncategorizedClinicalDocument.objects.filter(Q(review_mode=ReviewMode.REVIEW_REQUIRED), (Q(review__committer__isnull=True) | Q(review__entered_in_error__isnull=False)))
    ```
##  Delegations 
A document review can be delegated to another staff member or team. The delegations for a document are available through two accessors:
    ```python
    from canvas_sdk.v1.data import UncategorizedClinicalDocument
    document = UncategorizedClinicalDocument.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
    # The full delegation history, oldest first.
    history = document.delegations
    # The current active delegation, or None when the document is with its owner.
    current = document.active_delegation
    ```
See [DocumentReviewDelegation](/sdk/data-document-review-delegation/) for the delegation model and the `DOCUMENT_DELEGATED` event.
##  Document codings 
The `code` field comes from the document's type, which is drawn from a fixed list rather than set freely — either the type selected in Data Integration, or, when a document is created through the FHIR [DocumentReference](/api/documentreference/) endpoint, the LOINC code supplied in `type.coding`, which must match one of the codes below. Every coding uses the LOINC system (`http://loinc.org`). The document types stored as uncategorized clinical documents are:
Document type | Code | Display  
---|---|---  
Care Management Documents | 91983-7 | Care management note  
Clinical Patient Intake Form | 64285-0 | Medical history screening form  
Emergency Department Report | 96335-5 | Emergency department Summary note  
External Medical Records | 11503-0 | Medical records  
Home Care Report | 75503-3 | Patient's home Note  
Hospital Discharge Summary | 34105-7 | Hospital Discharge summary  
Hospital History and Physical | 47039-3 | Hospital Admission history and physical note  
Nursing Home | 34113-1 | Nursing facility Note  
Operative Report | 11504-8 | Surgical operation note  
Physical Exam Documents | 51848-0 | Evaluation note  
Prescription Refill Request | 57833-6 | Prescription for medication  
Rehabilitation Report | 34823-5 | Physical medicine and rehab Note  
Uncategorized Clinical Document | 34109-9 | Note  
In Office Testing Documents | — | none  
> **Warning:** In Office Testing Documents have no coding assigned, so their `code` is `None`. Filtering on `code` silently excludes them, and because the FHIR endpoint identifies a document's type by its LOINC code, they can only be created through Data Integration. 
Administrative document types are stored as [PatientAdministrativeDocument](/sdk/data-patient-administrative-document/) instead. Lab reports, imaging reports and specialist consult reports have their own models, so their codings never appear here.
##  The document reference 
`UncategorizedClinicalDocument` carries the document's type, review state and comments, not the file. Canvas stores the file on a [DocumentReference](/sdk/data-document-reference/#the-related-object) pointing back at the record, which is also how the document appears in the FHIR API.
To read it, resolve the [ContentType](/sdk/data-content-type/) at runtime from its stable `app_label` and `model` — never hardcode the per-environment `dbid` — and match `object_id` against the record's `dbid`:
    ```python
    from canvas_sdk.v1.data import ContentType, DocumentReference, UncategorizedClinicalDocument
    record = UncategorizedClinicalDocument.objects.get(
        id="d2194110-5c9a-4842-8733-ef09ea5ead11"
    )
    content_type = ContentType.objects.filter(
        app_label="api", model="uncategorizedclinicaldocument"
    ).first()
    document = DocumentReference.objects.filter(
        content_type=content_type, object_id=record.dbid
    ).first()
    url = document.document_url if document else None
    ```
> **Info:** `object_id` holds the related record's integer `dbid`, not its UUID `id`. A document marked entered-in-error keeps its document reference, with the status carried across, so check `status` if that matters to you. 
##  Attributes 
###  UncategorizedClinicalDocument 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
patient | [Patient](/sdk/data-patient/#patient)  
originator | [CanvasUser](/sdk/data-canvasuser)  
assigned_by | [CanvasUser](/sdk/data-canvasuser)  
review | UncategorizedClinicalDocumentReview  
team | [Team](/sdk/data-team/#team)  
code | [DocumentCoding](/sdk/data-patient-administrative-document/#documentcoding)  
name | String  
review_mode | [DocumentReviewMode](/sdk/data-enumeration-types/#documentreviewmode)  
junked | Boolean  
requires_signature | Boolean  
assigned_date | DateTime  
team_assigned_date | DateTime  
original_date | Date  
comment | String  
priority | Boolean  
###  UncategorizedClinicalDocumentReview 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
originator | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
internal_comment | String  
message_to_patient | String  
status | String  
patient | [Patient](/sdk/data-patient/#patient)  
patient_communication_method | String  
reports | QuerySet[UncategorizedClinicalDocument]
----- END PAGE https://docs.canvasmedical.com/sdk/data-uncategorized-clinical-document/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-vaccine/
##  Introduction 
The `Vaccine` model represents an entry in a Canvas instance's vaccine catalog — what a provider can choose when documenting an [Immunize](/sdk/commands/#immunize) command. `VaccineLot` represents a physical lot of one of those vaccines, along with how many doses remain on hand.
##  Basic usage 
A vaccine carries the CPT and CVX codes that identify it. The CVX code is on the vaccine; the CPT codes come from its charges, and those charges are what produce the billing line item when an [Immunize](/sdk/commands/#immunize) command is committed.
    ```python
    from canvas_sdk.v1.data import Vaccine
    vaccine = Vaccine.objects.filter(active=True, cvx_code="135").first()
    print(vaccine.cvx_code, [charge.cpt_code for charge in vaccine.charges.all()])
    # 135 ["90662"]
    ```
Each physical lot of a vaccine tracks how many doses remain, and committing an Immunize command decrements that count:
    ```python
    from canvas_sdk.v1.data import VaccineLot
    lot = VaccineLot.objects.filter(lot_number="LOT-135-001").first()
    print(lot.vaccine.short_name, lot.on_hand_inventory, lot.expiration_date)
    # Fluzone High-Dose 25 2027-06-30
    ```
`mvx_code` holds a CDC MVX manufacturer code. The codes are declared as the field's choices, so Django's display helper resolves the manufacturer name:
    ```python
    from canvas_sdk.v1.data import VaccineLot
    lot = VaccineLot.objects.filter(lot_number="LOT-135-001").first()
    print(lot.mvx_code, "->", lot.get_mvx_code_display())
    # ASZ -> AstraZeneca
    ```
Some instances record a single stock figure on the vaccine itself rather than tracking lots. That value lives in `Vaccine.inventory` as free text and is independent of `VaccineLot.on_hand_inventory`.
##  Filtering 
A vaccine is selectable on a note when it is active **and** carries an active CPT charge. Filtering the same way keeps a plugin in step with what a provider would see:
    ```python
    from datetime import date
    from django.db.models import Q
    from canvas_sdk.v1.data import Vaccine
    today = date.today()
    selectable = Vaccine.objects.filter(
        Q(active=True),
        Q(charges__effective_date__lte=today),
        Q(charges__end_date__isnull=True) | Q(charges__end_date__gte=today),
    ).distinct()
    print([vaccine.short_name for vaccine in selectable])
    # ["Fluzone High-Dose", "Trumenba", "Prevnar 13™"]
    ```
Lots are administrable while they have doses on hand:
    ```python
    from canvas_sdk.v1.data import VaccineLot
    in_stock = VaccineLot.objects.filter(vaccine__cvx_code="135", on_hand_inventory__gt=0)
    print([(lot.lot_number, lot.on_hand_inventory) for lot in in_stock])
    # [("LOT-135-001", 25)]
    ```
##  Attributes 
###  Vaccine 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
payer | [Transactor](/sdk/data-coverage/#transactor)  
charges | QuerySet[[ChargeDescriptionMaster](/sdk/data-charge-description-master/#chargedescriptionmaster)]  
cvx_code | String  
name | String  
short_name | String  
inventory | String  
ndc_code | String  
mvx_code | VaccineManufacturer  
route | String  
active | Boolean  
units | Integer  
lots | QuerySet[VaccineLot]  
A vaccine may appear more than once for the same `cvx_code` — instances commonly carry a payer-specific entry alongside a general one. Use `payer` to tell them apart.
###  VaccineLot 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
vaccine | Vaccine  
lot_number | String  
ndc_code | String  
mvx_code | VaccineManufacturer  
expiration_date | Date  
diluent_lot_number | String  
diluent_expiration_date | Date  
starting_inventory | Integer  
quantity_adjustment | Integer  
adjustment_notes | String  
on_hand_inventory | Integer  
used_inventory | Integer  
`on_hand_inventory` is derived from `starting_inventory + quantity_adjustment - used_inventory`.
##  Enumeration types 
###  VaccineManufacturer 
CDC MVX manufacturer codes. Prefer `get_mvx_code_display()` over mapping these yourself.
Value | Label  
---|---  
ASZ | AstraZeneca  
BBI | Bharat Biotech International Limited  
BN | Bavarian Nordic A/S  
BTP | Biotest Pharmaceuticals Corporation  
CAN | CanSino Biologics, Inc  
DVC | DynPort Vaccine Company, LLC  
DVX | Dynavax, Inc  
GEO | GeoVax Labs, Inc  
GRF | Grifols  
IDB | ID Biomedical  
JNJ | Johnson and Johnson  
JSN | Janssen  
KED | Kedrion Biopharma  
KGC | Korea Green Cross Corporation  
MBL | Massachusetts Biologic Laboratories  
MDO | Medicago, Inc  
MED | MedImmune, Inc. (AstraZeneca)  
MIP | Emergent BioSolutions  
MOD | Moderna US, Inc  
MSD | Merck and Co., Inc  
MSP | MSP Vaccine Company - (partnership Merck and Sanofi Pasteur)  
NAB | NABI  
NVX | Novavax, Inc  
OTH | Other manufacturer  
PAX | Emergent Travel Health, Inc (Formerly PaxVax)  
PFR | Pfizer, Inc  
PMC | Sanofi Pasteur  
PSC | Protein Sciences  
SEQ | Seqirus  
SKB | GlaxoSmithKline  
SNV | Sinovac  
SPH | Sinopharm-Biotech  
TVA | TEVA Pharmaceuticals USA  
UNK | Unknown manufacturer  
VAL | Valneva  
VBI | VBI Vaccines, Inc  
WAL | Wyeth
----- END PAGE https://docs.canvasmedical.com/sdk/data-vaccine/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-value-sets/
##  Introduction 
The Canvas SDK includes a library of built-in Value Sets that can be used within plugins to assist with finding conditions or medications related to Electronic Clinical Quality Measures. Plugin developers can also create their own Value Sets and use them in the same manner as the Canvas built-in `ValueSet` classes.
Built-in Value Sets that can be imported into plugins can be found in the Canvas SDK open source repo [here](https://github.com/canvas-medical/canvas-plugins/tree/main/canvas_sdk/value_set/).
##  Usage 
**Filtering Conditions by Value Set**
Value Set classes can be used directly in the data module to query for conditions that are included within them. For example, to find if a patient has been diagnosed with a condition whose coding falls under a particular Value Set, the `find` method can be used as follows:
    ```python
    from logger import log
    from canvas_sdk.v1.data.patient import Patient
    from canvas_sdk.value_set.v2022.condition import EssentialHypertension
    patient = Patient.objects.get(id="6cbc40b408294a5f9b41f57ba1b2b487")
    patient_essential_hypertension_conditions = patient.conditions.find(EssentialHypertension)
    # The patient has been diagnosed with one or more conditions that match a coding within the EssentialHypertension value set
    if patient_essential_hypertension_conditions:
        for condition in patient_essential_hypertension_conditions:
            log.info(condition.codings.all().values())
    ```
**Filtering Medications by Value Set**
Similar to the `Condition` example above, the `find` method can also utilize Value Set classes to filter `Medication` records that fall under a value set:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    from canvas_sdk.value_set.v2022.medication import DementiaMedications
    from logger import log
    patient = Patient.objects.get(id="6cbc40b408294a5f9b41f57ba1b2b487")
    patient_dementia_medications = patient.medications.find(DementiaMedications)
    if patient_dementia_medications:
        for medication in patient_dementia_medications:
            log.info(medication.codings.all().values())
    ```
**Filtering with more than one Value Set**
Sometimes it may be desirable to filter using more than one Value Set. For example, finding all of a patient's conditions that belong within `EssentialHypertension` _or_ `DiagnosisOfHypertension`. In this case, the `find` supports the pipe (`|`) operator to filter conditions that match the codings in either Value Set:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    from canvas_sdk.value_set.v2022.condition import EssentialHypertension, DiagnosisOfHypertension
    from logger import log
    patient = Patient.objects.get(id="6cbc40b408294a5f9b41f57ba1b2b487")
    patient_hypertension_conditions = patient.conditions.find(EssentialHypertension | DiagnosisOfHypertension)
    if patient_hypertension_conditions:
        for condition in patient_hypertension_conditions:
            log.info(condition.codings.all().values())
    ```
##  Creating Custom Value Sets 
The Canvas SDK allows plugin developers to create their own ValueSet classes that can be used in the same manner as the examples above. To do so, one can import and inherit the base `ValueSet` class:
    ```python
    from canvas_sdk.value_set.value_set import ValueSet
    ```
A new class containing Python sets of coding values can be defined like so:
    ```python
    from canvas_sdk.value_set.value_set import ValueSet
    class MyCustomValueSet(ValueSet):
        VALUE_SET_NAME = "My Custom Value Set"
        ICD10CM = {
            "T2601XA",  # Burn of right eyelid and periocular area, initial encounter
        }
        SNOMEDCT = {
            "284537006",  # Eyelid burn (disorder)
        }
    ```
The valid code system constants that can be used to define sets of codes in Value Sets are:
Name | URL  
---|---  
`CPT` | `http://www.ama-assn.org/go/cpt`  
`HCPCSLEVELII` | `https://coder.aapc.com/hcpcs-codes`  
`CVX` | `http://hl7.org/fhir/sid/cvx`  
`LOINC` | `http://loinc.org`  
`SNOMEDCT` | `http://snomed.info/sct`  
`FDB` | `http://www.fdbhealth.com/`  
`RXNORM` | `http://www.nlm.nih.gov/research/umls/rxnorm`  
`ICD10` | `ICD-10`  
`NUCC` | `http://www.nucc.org/`  
`CANVAS` | `CANVAS`  
`INTERNAL` | `INTERNAL`  
`NDC` | `http://hl7.org/fhir/sid/ndc`  
The following code is an example of a custom `ValueSet` in use within a plugin:
    ```python
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from logger import log
    from canvas_sdk.v1.data.patient import Patient
    from canvas_sdk.value_set.value_set import ValueSet
    class MyCustomValueSet(ValueSet):
        VALUE_SET_NAME = "My Custom Value Set"
        ICD10CM = {
            "T2601XA",  # Burn of right eyelid and periocular area, initial encounter
        }
        SNOMEDCT = {
            "284537006",  # Eyelid burn (disorder)
        }
    class MyHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.PATIENT_UPDATED)
        def compute(self):
            patient = Patient.objects.get(id="6cbc40b408294a5f9b41f57ba1b2b487")
            custom_value_set_conditions = patient.conditions.find(MyCustomValueSet)
            for vs in custom_value_set_conditions:
                log.info(vs)
            return []
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/data-value-sets/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-visual-exam-finding/
##  Introduction 
The `VisualExamFinding` model represents a visual exam finding captured on a note. Each finding consists of a titled image along with a narrative description of the clinical observation.
##  Basic usage 
To get a visual exam finding by identifier, use the `get` method on the `VisualExamFinding` model manager:
    ```python
    from canvas_sdk.v1.data.visual_exam_finding import VisualExamFinding
    finding = VisualExamFinding.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    ```
If you have a patient object, the visual exam findings for a patient can be accessed with the `visual_exam_findings` attribute on a `Patient` object:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    findings = patient.visual_exam_findings.all()
    ```
If you have a note object, the visual exam findings for that note can be accessed with the `visual_exam_findings` attribute on a `Note` object:
    ```python
    from canvas_sdk.v1.data.note import Note
    note = Note.objects.get(id="89992c23-c298-4118-864a-26cb3e1ae822")
    findings = note.visual_exam_findings.all()
    ```
##  Accessing image files 
The `image_url` property returns a presigned S3 URL for securely accessing the image file. The URL is valid for 1 hour.
    ```python
    from canvas_sdk.v1.data.visual_exam_finding import VisualExamFinding
    finding = VisualExamFinding.objects.exclude(image="").first()
    # Returns a presigned S3 URL (valid for 1 hour), or None if no image is set
    url = finding.image_url
    ```
##  Filtering 
Visual exam findings can be filtered by any attribute that exists on the model.
###  By attribute 
Specify an attribute with `filter` to filter by that attribute:
    ```python
    from canvas_sdk.v1.data.visual_exam_finding import VisualExamFinding
    # Get all findings with a specific title
    findings = VisualExamFinding.objects.filter(title="Left forearm")
    ```
###  By patient 
    ```python
    from canvas_sdk.v1.data.visual_exam_finding import VisualExamFinding
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    findings = VisualExamFinding.objects.filter(patient=patient)
    ```
###  Committed findings 
The `committed` method returns visual exam findings that have been committed and not entered in error:
    ```python
    from canvas_sdk.v1.data.visual_exam_finding import VisualExamFinding
    committed_findings = VisualExamFinding.objects.committed()
    ```
##  Attributes 
###  VisualExamFinding 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
patient | [Patient](/sdk/data-patient/#patient)  
note | [Note](/sdk/data-note/#note)  
image | String (S3 key)  
title | String  
narrative | String  
originator | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
image_url | String (property) — presigned S3 URL
----- END PAGE https://docs.canvasmedical.com/sdk/data-visual-exam-finding/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data-vital-sign-reading/
##  Introduction 
The `VitalSignReading` model is the anchor for the [Vitals](/sdk/commands/#vitals) command — a set of vital-sign readings recorded on a Note for a Patient. The individual measurements (blood pressure, heart rate, temperature, weight, etc.) are stored as related `VitalSign` records, reachable via the `signs` attribute.
##  Basic usage 
To get a vital sign reading by identifier, use the `get` method on the `VitalSignReading` model manager:
    ```python
    from canvas_sdk.v1.data.vitals import VitalSignReading
    reading = VitalSignReading.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    ```
If you have a patient or note object, the readings can be accessed with the `vital_sign_readings` attribute:
    ```python
    from canvas_sdk.v1.data.patient import Patient
    from canvas_sdk.v1.data.note import Note
    patient = Patient.objects.get(id="1eed3ea2a8d546a1b681a2a45de1d790")
    readings = patient.vital_sign_readings.all()
    note = Note.objects.get(id="89992c23-c298-4118-864a-26cb3e1ae822")
    readings = note.vital_sign_readings.all()
    ```
If you have a patient ID, you can get the readings for the patient with the `for_patient` method:
    ```python
    from canvas_sdk.v1.data.vitals import VitalSignReading
    patient_id = "1eed3ea2a8d546a1b681a2a45de1d790"
    readings = VitalSignReading.objects.for_patient(patient_id)
    ```
##  Reading the individual measurements 
Each `VitalSignReading` has one or more `VitalSign` measurements, accessed with the `signs` attribute. Each `VitalSign` carries the measurement's LOINC code, name, value, and units:
    ```python
    from canvas_sdk.v1.data.vitals import VitalSignReading
    from logger import log
    reading = VitalSignReading.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    for sign in reading.signs.all():
        log.info(f"{sign.sign}: {sign.value} {sign.units} (LOINC {sign.loinc_num})")
    ```
`signs` includes the parts of a composite measurement as well as the measurement itself, so a blood pressure appears three times in the loop above. See Composite measurements to walk only the top-level readings.
##  Filtering 
Vital sign readings can be filtered by any attribute that exists on the model.
###  Committed readings 
The `committed` method returns readings that have been committed and not entered in error:
    ```python
    from canvas_sdk.v1.data.vitals import VitalSignReading
    committed_readings = VitalSignReading.objects.committed()
    ```
##  Attributes 
###  VitalSignReading 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
originator | [CanvasUser](/sdk/data-canvasuser)  
committer | [CanvasUser](/sdk/data-canvasuser)  
entered_in_error | [CanvasUser](/sdk/data-canvasuser)  
patient | [Patient](/sdk/data-patient/#patient)  
note | [Note](/sdk/data-note)  
date_recorded | DateTime  
signs | VitalSign[]  
###  VitalSign 
Field Name | Type  
---|---  
id | UUID  
dbid | Integer  
created | DateTime  
modified | DateTime  
reading | VitalSignReading  
date_recorded | DateTime  
loinc_num | String  
sign | String — one of the sign values  
sign_description | String  
value | String  
units | String  
source | String  
parent | VitalSign — the composite measurement this one is a part of, if any  
children | VitalSign[] — the parts of this measurement, if it is a composite  
##  Composite measurements 
Some measurements are recorded as a whole _and_ as their parts. The whole is stored as one `VitalSign` and each part as another, linked to it by `parent`; the reverse accessor is `children`. A measurement that stands on its own has `parent` set to `None` and no `children`.
The [Vitals](/sdk/commands/#vitals) command produces two of these:
  - `blood_pressure` — the combined reading, parent of the `systole` and `diastole` signs taken from it.
  - `oxygen_saturation` — parent of `inhaled_oxygen_concentration` and `inhaled_oxygen_flow_rate`.
Because the parts sit alongside the whole in `reading.signs`, iterating a reading naively counts a blood pressure three times. Filter on `parent` to walk only the top-level measurements:
    ```python
    from canvas_sdk.v1.data.vitals import VitalSignReading
    from logger import log
    reading = VitalSignReading.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    for sign in reading.signs.filter(parent__isnull=True):
        parts = ", ".join(f"{part.sign}={part.value}" for part in sign.children.all())
        log.info(f"{sign.sign}: {sign.value} {sign.units}" + (f" ({parts})" if parts else ""))
    ```
##  Sign values 
`VitalSign.sign` holds one of a fixed set of values — the ones below are those a Canvas workflow records. Canvas declares them as a `VitalSignChoices` enumeration internally, but that enumeration is **not** exported to plugins, so compare against the string value directly:
    ```python
    from canvas_sdk.v1.data.vitals import VitalSignReading
    reading = VitalSignReading.objects.get(id="b80b1cdc-2e6a-4aca-90cc-ebc02e683f35")
    weights = [sign for sign in reading.signs.all() if sign.sign == "weight"]
    ```
`sign_description` carries a human-readable label for the same measurement, so prefer it for display and reserve `sign` for matching.
###  Where each value comes from 
Not every value is produced by every workflow, so which ones you see depends on how the vitals were recorded:
  - **The[Vitals](/sdk/commands/#vitals) command** writes `height`, `weight`, `waist_circumference`, `body_temperature`, `blood_pressure`, `systole`, `diastole`, `pulse`, `pulse_rhythm`, `respiration_rate`, `oxygen_saturation`, `inhaled_oxygen_concentration`, `inhaled_oxygen_flow_rate`, `supplemental_oxygen` and `note`. Blood pressure is stored three times over — once as the combined `blood_pressure` reading and once each as `systole` and `diastole`.
  - **A committed pediatric physical exam questionnaire** records `length` and `head_circumference_tape_measure`, taken from the answers carrying those LOINC codes.
Derived measurements are **not** `VitalSign` records. When a height, weight or length is recorded, Canvas calculates BMI from the height and weight and stores the results — the BMI-for-age, head-circumference and weight-for-height percentiles — as [Observation](/sdk/data-observation/) records attached to the reading, because each is computed from more than one measurement. Read them there rather than looking for a `sign`.
Value | Label  
---|---  
blood_pressure | Blood Pressure  
systole | Systole  
diastole | Diastole  
pulse | Pulse  
pulse_rhythm | Pulse Rhythm  
respiration_rate | Respiration Rate  
body_temperature | Body Temperature  
oxygen_saturation | Oxygen Saturation  
supplemental_oxygen | Supplemental Oxygen  
inhaled_oxygen_concentration | Inhaled Oxygen Concentration  
inhaled_oxygen_flow_rate | Inhaled Oxygen Flow Rate  
weight | Weight  
height | Height  
length | Length  
head_circumference_tape_measure | Head Circumference by Tape Measure  
waist_circumference | Waist Circumference  
note | Note  
----- END PAGE https://docs.canvasmedical.com/sdk/data-vital-sign-reading/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/data/
The data module provides you with data to compute on. It provides curated, secure access to both PHI (e.g. patient data) and non-PHI (e.g. staff and practice-level data), representing the current state of your target Canvas instance. The module's classes offer convenience methods and operators that make business logic and clinical logic easy to express with standard terminologies like ICD-10, SNOMED-CT, CPT, and the like.
Data module classes are Django ORM models, which allow easy retrieval of data at runtime through Django's expressive [QuerySet API](https://docs.djangoproject.com/en/5.1/ref/models/querysets/).
Access to these models is **read-only** , and mutations to them are allowed only via use of [Effects](/sdk/effects/) and the FHIR API.
Use the [Custom Data](/sdk/custom-data/) features for creating and maintaining your plugin's own data.
The pages below provide listings of the models, their attributes, and examples of usage.
[ AllergyIntolerance Harmful or undesired physiological responses associated with exposure to a substance. ](/sdk/data-allergy-intolerance/) [ Application A plugin application. ](/sdk/data-application/) [ Appointment A scheduled meeting between a patient and a provider. ](/sdk/data-appointment/) [ Assessment Clinical assessment of a patient's condition. ](/sdk/data-assessment/) [ BannerAlert An alert notification linked to a patient. ](/sdk/data-banner-alert/) [ BillingLineItem A billable code linked to a patient note. ](/sdk/data-billing-line-item/) [ BusinessLine A group of patients that share a common brand under an organization. ](/sdk/data-business-line/) [ Calendar Calendars associated with providers. ](/sdk/data-calendar/) [ CancelPrescription A request to cancel a patient's prescription (the CancelPrescription command). ](/sdk/data-cancel-prescription/) [ CancelPrescriptionResponse The response to a CancelPrescription request. ](/sdk/data-cancel-prescription-response/) [ CanvasUser User accounts associated with other records. ](/sdk/data-canvasuser/) [ CareTeam Teams assigned for patient care. ](/sdk/data-care-team/) [ Change Medication A record of a Change Medication command, used to update a medication's directions (sig) without issuing a new prescription. ](/sdk/data-change-medication/) [ ChargeDescriptionMaster Billing charges in Canvas that can be added to the note footer. ](/sdk/data-charge-description-master/) [ ChartSectionReview Reviewed chart sections captured on a note, with their pre-rendered title and narrative content. ](/sdk/data-chart-section-review/) [ Claim A healthcare claim. ](/sdk/data-claim/) [ Command Structured units of documentation in a patient's chart. ](/sdk/data-command/) [ CommonEnumerationTypes Common choice classes used in multiple models. ](/sdk/data-enumeration-types/) [ CompoundMedication Compound medications, which are custom-made medications tailored to a patient's specific needs. ](/sdk/data-compound-medication/) [ Condition Condition, diagnosis, or reason for seeking medical attention. ](/sdk/data-condition/) [ ContentType Django content type ids used for generic relations and permalink generation. ](/sdk/data-content-type/) [ Coverage Patient insurance coverage. ](/sdk/data-coverage/) [ DetectedIssue Actual or potential clinical issue with or between one or more active or proposed clinical actions for a patient. ](/sdk/data-detected-issue/) [ Device Type of a manufactured item that is used in the provision of healthcare. ](/sdk/data-device/) [ DiagnosticView A saved set of lab tests and questionnaire codes whose timeseries can be embedded in a note with the Reference command. ](/sdk/data-diagnostic-view/) [ DocumentReference References to documents stored in Canvas, with presigned URL support. ](/sdk/data-document-reference/) [ DocumentReviewDelegation A hand-off of a document review from one staff member (or team) to another, with signature consent. ](/sdk/data-document-review-delegation/) [ EducationalMaterial Patient educational material shared from a note via the Educational Material command. ](/sdk/data-educational-material/) [ EligibilityResponse A coverage eligibility (270/271) request and response, with the derived check status. ](/sdk/data-eligibility-response/) [ EligibilitySummary Summary of copay and coinsurance for a Coverage. ](/sdk/data-coverage/#eligibilitysummary) [ Encounter A patient Encounter connected to a Note in Canvas. ](/sdk/data-encounter/) [ ExternalEvent External clinical events from ADT feeds such as admissions, discharges, and transfers. ](/sdk/data-external-event/) [ Facility A location where healthcare services are provided. ](/sdk/data-facility/) [ FamilyHistory A patient's family medical history — conditions recorded for a relative. ](/sdk/data-family-history/) [ FollowUp A requested follow-up (recall) recorded on a note via the follow_up command. ](/sdk/data-follow-up/) [ Goal A goal for a patient. ](/sdk/data-goal/) [ HistoryOfPresentIllness The History of Present Illness (HPI) narrative recorded on a note. ](/sdk/data-history-present-illness/) [ Imaging Analysis of imaging tests to obtain information about the health of a patient. ](/sdk/data-imaging/) [ ImagingReportTemplate Templates used for imaging reports, defining fields and options. ](/sdk/data-imaging-report-template/) [ Immunization A record of a vaccination that is being administered to a patient, either now, in the past, or in the future. ](/sdk/data-immunization/) [ Instruction An Instruct command committed in a patient's note — clinical guidance such as cessation counseling or dietary instructions. ](/sdk/data-instruction/) [ IntegrationTask Incoming documents that need processing, including faxes, uploads, and portal submissions. ](/sdk/data-integration-task/) [ Invoice A patient statement generated for a patient or guarantor, with its total, delivery method, and status. ](/sdk/data-invoice/) [ LabPartner Lab partners and the tests they offer within Canvas. ](/sdk/data-lab-partner-and-test/) [ LabReportTemplate Templates for point-of-care labs and custom lab reports, defining fields and options. ](/sdk/data-lab-report-template/) [ Labs Analysis of clinical specimens to obtain information about the health of a patient. ](/sdk/data-labs/) [ Letter Patient correspondence letters created within Canvas. ](/sdk/data-letter/) [ LetterActionEvent Actions taken on a letter, such as printing or faxing. ](/sdk/data-letter-action-event/) [ Medication A record of a medication that is being consumed by a patient, either now, in the past, or in the future. ](/sdk/data-medication/) [ Medication History A record of a patient's medication history, including medications that were taken in the past but are no longer active. ](/sdk/data-medication-history/) [ Medication Statement A record of a medication statement by a patient from the past. ](/sdk/data-medication-statement/) [ Message Messages sent to and from Canvas. ](/sdk/data-message/) [ Note Clinical notes on patient charts. ](/sdk/data-note/) [ Observation Measurements and simple assertions made about a patient. ](/sdk/data-observation/) [ Organization The clinical organization present in the Canvas EMR. ](/sdk/data-organization/) [ OrganizationalEntity External entities, such as service providers, referenced by a patient's external care team members. ](/sdk/data-organizational-entity/) [ Patient Data used to categorize individuals for identification, records matching, and other purposes. ](/sdk/data-patient/) [ PatientAdministrativeDocument Patient-facing administrative documents, such as signed consent forms and statements. ](/sdk/data-patient-administrative-document/) [ PatientConsent Documented patient consents that ensure legal compliance and protect patient rights. ](/sdk/data-patient-consent/) [ PatientGroup A collection of patients. ](/sdk/data-patient-group/) [ PayorSpecificCharge A billing charge specific to a certain transactor in Canvas. ](/sdk/data-payor-specific-charge/) [ Plan A Plan (plan of care) narrative recorded on a note. ](/sdk/data-plan/) [ PluginCommand Custom commands registered by plugins via the manifest configuration. ](/sdk/data-plugin-command/) [ Posting Payments and postings associated with healthcare claims. ](/sdk/data-posting/) [ PracticeLocation The practice locations present in the Canvas EMR. ](/sdk/data-practicelocation/) [ Prescription The practice locations present in the Canvas EMR. ](/sdk/data-prescription/) [ PrescriptionChangeRequest An incoming Surescripts request to change a prescription, with its medication codings. ](/sdk/data-prescription-change-request/) [ PrescriptionChangeResponse A response (approve/deny) to a Surescripts prescription change request. ](/sdk/data-prescription-change-response/) [ Procedure A procedure performed on or ordered for a patient, with its CPT codings. ](/sdk/data-procedure/) [ ProtocolCurrent The current state of clinical protocols applied to patients within Canvas. ](/sdk/data-protocol-current/) [ ProtocolOverride A record of a protocol being snoozed for a patient. ](/sdk/data-protocol-override/) [ Questionnaire Groups of coded questions and the coded patient responses. ](/sdk/data-questionnaire/) [ ReasonForVisit The reason for a patient's visit. ](/sdk/data-reason-for-visit/) [ Referral A referral directing a specific patient to another provider or specialist. ](/sdk/data-referral/) [ RefillRequest An incoming request to refill a patient's medication and its responding prescriptions. ](/sdk/data-refill-request/) [ RemoveAllergyEvent A record of an allergy being removed via the remove_allergy command. ](/sdk/data-remove-allergy-event/) [ ResolveConditionEvent A record of a condition being resolved via the resolve_condition command. ](/sdk/data-resolve-condition-event/) [ ServiceProvider Data associated with Service Providers. ](/sdk/data-serviceprovider/) [ Snapshot Images captured via the Canvas iOS application. ](/sdk/data-snapshot/) [ SpecialtyReportTemplate Templates for specialty and referral reports, including specialty taxonomy codes. ](/sdk/data-specialty-report-template/) [ Staff Data associated with Staff members. ](/sdk/data-staff/) [ Stop Medication Event A record of a Stop Medication Event, when a medication is removed from a patient's medication list. ](/sdk/data-stop-medication-event/) [ Task Data associated with Tasks. ](/sdk/data-task/) [ Team Data associated with Teams. ](/sdk/data-team/) [ Uncategorized Clinical Documents Data associated with uncategorized clinical documents and their reviews. ](/sdk/data-uncategorized-clinical-document/) [ Vaccine A vaccine in the instance's catalog and the lots of it held in inventory. ](/sdk/data-vaccine/) [ ValueSets Lists of codes and terms from various clinical coding systems grouped by a defining concept. ](/sdk/data-value-sets/) [ VisualExamFinding Visual exam findings captured on a note — a titled image with a narrative description. ](/sdk/data-visual-exam-finding/) [ VitalSignReading Vital-sign readings recorded via the vitals command — the reading anchor and its individual measurements. ](/sdk/data-vital-sign-reading/)
----- END PAGE https://docs.canvasmedical.com/sdk/data/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/default-homepage-effect/
##  Overview 
This allows developers to set a provider's default homepage in Canvas. The default homepage is the page that a provider sees when they log in to Canvas. This effect can be used to set the default homepage to a specific page or a plugin application. For more guidance please reference "[How to set a default homepage for the provider application](/guides/set-default-homepage/)"
    ```python
    from canvas_sdk.effects.default_homepage import DefaultHomepageEffect
    DefaultHomepageEffect(page=DefaultHomepageEffect.Pages.PATIENTS).apply()
    ```
    ```python
    from canvas_sdk.effects.default_homepage import DefaultHomepageEffect
    DefaultHomepageEffect(application_identifier="app_identifier").apply()
    ```
##  Structure 
###  **Pages**
An enumeration of pages that can be set as the default homepage:
Value  
---  
`PATIENTS`  
`SCHEDULE`  
`REVENUE`  
`CAMPAIGNS`  
`DATA_INTEGRATION`  
###  **DefaultHomepage**
A DefaultHomepage effect consists of the following properties:
####  Attributes 
Attribute | Type | Description  
---|---|---  
`page` | `Pages \| None` | Optional page  
`application_identifier` | `str \| None` | Optional application identifier  
If both `page` and `application_identifier` are provided, `application_identifier` will take precedence and the default homepage will be set to the specified application. If neither is provided, the default homepage will be set to the Canvas default homepage.
----- END PAGE https://docs.canvasmedical.com/sdk/default-homepage-effect/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-application-notification-badge/
Notification badges let your plugin surface a count on an [application](/sdk/handlers-applications/) icon — the small number that indicates, for example, how many unread items are waiting. Badges are shown for applications scoped [`global`](/sdk/handlers-applications/#application-scopes) or [`patient_specific`](/sdk/handlers-applications/#application-scopes) — on their icon in the app drawer, or, when the application sets `show_in_panel`, on the panel alongside the other panel buttons — and for [`provider_menu_item`](/sdk/handlers-applications/#application-scopes) applications, next to their label in the provider menu. Applications in other scopes (`full_chart`, `portal_menu_item`, and the Provider Companion scopes) do not display badges.
There are two ways a badge is set:
  - **On load** — override `compute_notification_badge()` on your `Application` handler to provide the initial count shown when Canvas loads applications. See [Notification Badges](/sdk/handlers-applications/#notification-badges) on the Applications handler page.
  - **Live updates** — emit an `ApplicationNotificationBadge` effect from any event handler to update the count in real time, without the user reloading the page. This is what the rest of this page covers.
##  Setting a badge 
`ApplicationNotificationBadge` is a fluent builder. Construct it with the target application's identifier, optionally `.filter(...)` to target patients, then call `.broadcast(...)` to produce the effect.
Method / Attribute |  | Type | Description  
---|---|---|---  
`application_identifier` | required | String | Passed to the constructor. Must match the application's `class` string declared in `CANVAS_MANIFEST.json` — the `<module path>:<ClassName>` value (identical to the handler's `identifier`). An unknown identifier raises a validation error.  
`count` | required | Integer | Passed to `.broadcast()`. The badge value to display. Must be `>= 0`; a count of `0` clears the badge.  
`staff_ids` | optional | list[String] | Passed to `.broadcast()`. [Staff](/sdk/data-staff/) keys that should see the update.  
`patient_ids` | optional | list[String] | Passed to `.filter()`. [Patient](/sdk/data-patient/) keys whose chart context the update applies to.  
The `application_identifier` is the application's `class` string from `CANVAS_MANIFEST.json` (`<module path>:<ClassName>`). For example, an `InboxApp` defined in `my_plugin/apps/inbox.py` and registered like this:
    ```json
    "applications": [
      {
        "class": "my_plugin.apps.inbox:InboxApp",
        "name": "Inbox",
        "description": "Unread items inbox",
        "icon": "/assets/inbox.png",
        "scope": "global"
      }
    ]
    ```
is targeted by that same `class` string:
    ```python
    from canvas_sdk.effects.application_notification_badge import ApplicationNotificationBadge
    # Set a badge of 3 for a specific staff member.
    ApplicationNotificationBadge("my_plugin.apps.inbox:InboxApp").broadcast(count=3, staff_ids=["staff-id"])
    ```
##  Targeting 
`staff_ids` and `patient_ids` control who sees the update. An empty list means "all" on that axis:
`staff_ids` | `patient_ids` | Who sees the badge  
---|---|---  
set | empty | The listed staff, on any patient (and on global views).  
empty | set | Staff currently viewing the listed patients' charts.  
set | set | The listed staff, but only while viewing the listed patients' charts.  
empty | empty | All staff, all patients (a system-wide update).  
Patients are never subscribers themselves — `patient_ids` scopes the badge to a patient's chart, where staff viewing that chart will see it.
> **Note on "all patients" (empty`patient_ids`):** the update is delivered **live** only to charts a staff member currently has open. Other patients' charts reflect the new value the next time they're loaded, via `compute_notification_badge()`. So for a badge that should read the same across every patient, have `compute_notification_badge()` return a patient-independent count (ignore the patient in `self.event.context`). A push then keeps the open chart live, and the load-time hook covers the rest.
    ```python
    from canvas_sdk.effects.application_notification_badge import ApplicationNotificationBadge
    # Show a badge to staff viewing a specific patient's chart.
    ApplicationNotificationBadge("my_plugin.apps.patient_labs:PatientLabsApp").filter(
        patient_ids=["patient-id"]
    ).broadcast(count=5)
    # Combine: only the on-call provider, and only on this patient's chart.
    ApplicationNotificationBadge("my_plugin.apps.patient_labs:PatientLabsApp").filter(
        patient_ids=["patient-id"]
    ).broadcast(count=1, staff_ids=["staff-id"])
    ```
##  Reacting to events 
The most common pattern is updating a badge in response to a domain event. Here a handler recomputes a staff member's inbox count whenever a task is created and pushes the new value live:
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.application_notification_badge import ApplicationNotificationBadge
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.v1.data.task import Task, TaskStatus
    class InboxBadgeHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.TASK__CREATED)
        def compute(self) -> list[Effect]:
            task = Task.objects.get(id=self.event.target.id)
            assignee = task.assignee
            if not assignee:
                return []
            open_count = Task.objects.filter(assignee=assignee, status=TaskStatus.OPEN).count()
            return [
                ApplicationNotificationBadge("my_plugin.apps.inbox:InboxApp").broadcast(
                    count=open_count,
                    staff_ids=[assignee.id],
                )
            ]
    ```
##  Clearing a badge 
Broadcast a `count` of `0` to remove the badge from the icon:
    ```python
    from canvas_sdk.effects.application_notification_badge import ApplicationNotificationBadge
    ApplicationNotificationBadge("my_plugin.apps.inbox:InboxApp").broadcast(count=0, staff_ids=["staff-id"])
    ```
> **Note:** To set the badge value shown when applications first load (rather than in response to an event), override `compute_notification_badge()` on your `Application` handler. See [Notification Badges](/sdk/handlers-applications/#notification-badges).
----- END PAGE https://docs.canvasmedical.com/sdk/effect-application-notification-badge/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-appointment-labels/
#  Appointment Label Effects 
The appointment label effects provide programmatic management of labels in Canvas. Labels serve as visual indicators and categorization tools, enabling automated workflows and improved organization for appointments.
##  Overview 
Labels are a powerful way to categorize and track appointments. Canvas supports up to 3 labels per appointment, and these effects allow plugins to automatically manage labels based on business logic.
##  AddAppointmentLabel Effect 
The `AddAppointmentLabel` effect adds one or more labels to an existing appointment.
###  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`appointment_id` | `str` | ID of the appointment to add labels to | Yes  
`labels` | `set[str]` | Set of label names to add (1-3 labels total per appointment) | Yes  
###  apply() → Effect 
Adds the specified labels to the appointment.
####  Returns 
An `Effect` object configured for adding appointment labels.
####  Behavior 
  - Labels are added to the appointment if the total count doesn't exceed 3
  - Labels are automatically sorted for consistency
  - Duplicate labels are ignored (labels are stored as a set)
  - Validates the appointment exists before adding labels
  - Validates label names are non-empty strings
  - Returns an error if adding labels would exceed the 3-label limit
####  Example Usage 
    ```python
    from canvas_sdk.effects.note.appointment import AddAppointmentLabel
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        RESPONDS_TO = [EventType.Name(EventType.APPOINTMENT_CREATED)]
        def compute(self):
            # Add labels to an appointment
            effect = AddAppointmentLabel(
                appointment_id="appointment-uuid",
                labels={"URGENT", "FOLLOW_UP"}
            )
            return [effect.apply()]
    ```
If more than three labels are attempted to be added, a `ValidationError` will be raised.
    ```python
    from canvas_sdk.effects.note.appointment import AddAppointmentLabel
    from canvas_sdk.exceptions import ValidationError
    def handle_validation_errors():
        # Example of handling validation errors
        try:
            effect = AddAppointmentLabel(
                appointment_id="invalid-id",
                labels={"LABEL1", "LABEL2", "LABEL3", "LABEL4"}  # Would exceed limit
            )
            return [effect.apply()]
        except ValidationError as e:
            # Handle validation errors
            return []
    ```
* * *
##  RemoveAppointmentLabel Effect 
The `RemoveAppointmentLabel` effect removes one or more labels from an existing appointment.
###  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`appointment_id` | `str` | ID of the appointment to remove labels from | Yes  
`labels` | `set[str]` | Set of label names to remove | Yes  
###  apply() → Effect 
Removes the specified labels from the appointment.
####  Returns 
An `Effect` object configured for removing appointment labels.
####  Behavior 
  - Removes the specified labels from the appointment
  - Non-existent labels are ignored (no error thrown)
  - Validates the appointment exists before removing labels
####  Example Usage 
    ```python
    from canvas_sdk.effects.note.appointment import RemoveAppointmentLabel
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        RESPONDS_TO = [EventType.Name(EventType.APPOINTMENT_LABEL_REMOVED)]
        def compute(self):
            # Remove labels from an appointment
            effect = RemoveAppointmentLabel(
                appointment_id="appointment-uuid",
                labels={"CANCELLED", "RESCHEDULED"}
            )
            return [effect.apply()]
    ```
* * *
##  Implementation Details 
###  Label Constraints 
  - **Maximum labels** : 3 labels per appointment (enforced by validation)
  - **Label format** : Labels are strings, automatically sorted for consistency
  - **Uniqueness** : Labels are stored as a set, preventing duplicates
  - **Case sensitivity** : Label names are case-sensitive
###  Validation Messages 
The effects provide clear error messages for common issues:
  - `"Appointment {appointment_id} does not exist"` \- When appointment ID is invalid
  - `"Limit reached: Only 3 appointment labels allowed. Attempted to add {count} label(s) to appointment with {existing} existing label(s)."` \- When label limit would be exceeded
These effects work seamlessly with appointment label events:
  - `APPOINTMENT_LABEL_ADDED` \- Fired when labels are added
  - `APPOINTMENT_LABEL_REMOVED` \- Fired when labels are removed
For more information on these events, see [Appointment Events](/sdk/events/#appointments).
##  Related Documentation 
  - [Appointment Events](/sdk/events/#appointments) \- Event documentation
  - [Appointment Coverage Label Example](/sdk/examples/appointment_coverage_label/) \- Real-world example plugin
----- END PAGE https://docs.canvasmedical.com/sdk/effect-appointment-labels/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-appointment-metadata/
The `AppointmentMetadata` effect provides a flexible key-value storage system for appointment-specific data within the Canvas system. This effect enables the creation and updating of custom metadata entries associated with appointment records. This allows for extensible appointment information storage beyond standard scheduling fields.
##  Overview 
Appointment metadata serves as a powerful extension mechanism for storing custom appointment-related information that doesn't fit within the standard appointment data model. It uses the `.upsert(value)` method to apply a value to the key attributed with the Metadata effect object.
##  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`appointment_id` | `str` | Id of the [Appointment(/sdk/data-appointment/)] record to associate metadata with | Yes  
`key` | `str` | Unique identifier for the metadata entry within the appointment context | Yes  
##  Methods 
###  upsert(value: str) → Effect 
Creates or updates a metadata entry for the specified appointment and key combination.
####  Parameters 
Parameter | Type | Description | Required  
---|---|---|---  
`value` | `str` | The metadata value to store | Yes  
####  Returns 
An `Effect` object configured for upserting appointment metadata.
####  Behavior 
  - If a metadata entry with the specified key already exists for the appointment, it will be updated with the new value
  - If no entry exists, a new metadata entry will be created
  - The operation is idempotent - repeated calls with the same key and value will not create duplicate entries
##  Implementation Details 
###  Validation 
The effect performs comprehensive validation before execution:
  1. **Appointment Existence Validation** : Verifies that the referenced appointment exists in the system
  - Queries the appointment database to confirm the `appointment_id` corresponds to an existing appointment record
  - Returns a descriptive error if the appointment is not found
  1. **Field Validation** : Ensures all required fields are provided and properly formatted
  - Both `appointment_id` and `key` must be non-empty strings
  - The `value` parameter in the `upsert` method must be provided
###  Data Structure 
The effect payload is structured as JSON with the following schema:
    ```json
    {
      "data": {
        "appointment_id": "appointment-id",
        "key": "metadata-key",
        "value": "metadata-value"
      }
    }
    ```
##  Example Usage 
###  Basic Usage 
    ```python
    from canvas_sdk.effects.appointments_metadata.base import AppointmentsMetadata
    # Create a metadata entry for appointment state
    metadata = AppointmentsMetadata(
        appointment_id="550e8400e29b41d4a716446655440001",
        key="state"
    )
    # Upsert the metadata value
    effect = metadata.upsert("CA")
    ```
##  Best Practices 
###  Key Naming Conventions 
  1. **Use Descriptive Names** : Choose keys that clearly indicate the purpose of the metadata
  - Good: `external_mrn`, `preferred_pharmacy_id`, `risk_score_diabetes`
  - Avoid: `data1`, `temp`, `misc`
  1. **Namespace Your Keys** : When building integrations or modules, prefix keys to avoid collisions
  - Example: `integration_patient_id`, `module_diabetes_last_a1c_date`
###  Value Storage 
  1. **String Serialization** : All values are stored as strings. For complex data types:
         ```python
         import json
         from canvas_sdk.effects.appointments_metadata.base import AppointmentsMetadata
         metadata = AppointmentsMetadata(
             appointment_id="550e8400e29b41d4a716446655440001",
             key="result"
         )
         complex_data = {"scores": [85, 92, 78], "average": 85.0}
         metadata.upsert(json.dumps(complex_data))
         ```
  2. **Boolean Values** : Store as "true" or "false" strings for consistency
         ```python
         from canvas_sdk.effects.appointments_metadata.base import AppointmentsMetadata
         consented = True
         metadata = AppointmentsMetadata(
             appointment_id="550e8400e29b41d4a716446655440001",
             key="boolean_value"
         )
         metadata.upsert("true" if consented else "false")
         ```
##  Notes 
  - Metadata entries are appointment-specific and isolated - the same key can have different values for different appointments
  - There is no built-in versioning; updating a key overwrites the previous value
  - The system does not enforce any schema on metadata values - validation is the responsibility of the implementing code
----- END PAGE https://docs.canvasmedical.com/sdk/effect-appointment-metadata/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-banner-alerts/
The Canvas SDK allows you to place Banners on the Canvas UI.
##  Adding a Banner Alert 
To add a banner alert, import the `AddBannerAlert` class and create an instance of it.
Attribute |  | Type | Description  
---|---|---|---  
patient_id | required (if patient_filter is not provided) | String | The id of the [patient](/sdk/data-patient/) the alert should be associated with.  
patient_filter | required (if patient_id is not provided) | String | Patient queryset filters to apply the effect to multiple patients. For example, `{"active": True}` will apply to the effect to all active patients  
key | required | String | An identifier that categorizes the alert.  
narrative | required | String | The content of the alert. Maximum 90 characters.  
placement | required | list[Placement] | List of areas the alert should show.  
intent | optional | Intent | Affects the styling of the alert.  
href | optional | String | If given, the alert will appear as a link to this URL.  
    ```python
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.effects.banner_alert import AddBannerAlert
    class MyHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.PATIENT_UPDATED)
        def compute(self):
            banner = AddBannerAlert(
                patient_id=self.target,
                key="test-alert",
                narrative="This is only a test.",
                placement=[
                    AddBannerAlert.Placement.CHART,
                    AddBannerAlert.Placement.APPOINTMENT_CARD,
                    AddBannerAlert.Placement.SCHEDULING_CARD,
                ],
                intent=AddBannerAlert.Intent.INFO,
                href="https://docs.canvasmedical.com",
            )
            return [banner.apply()]
    ```
To apply the effect to all active patients when a plugin is created or updated, include the `PLUGIN_CREATED` and/or `PLUGIN_UPDATED` events in the `RESPONDS_TO` list. Additionally, `patient_filter` can be used (instead of `patient_id`) on the `AddBannerAlert` class.
    ```python
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.effects.banner_alert import AddBannerAlert
    class MyHandler(BaseHandler):
        RESPONDS_TO = [
            EventType.Name(EventType.PATIENT_UPDATED),
            EventType.Name(EventType.PLUGIN_CREATED),
            EventType.Name(EventType.PLUGIN_UPDATED),
        ]
        def compute(self):
            banner = AddBannerAlert(
                key="test-alert",
                narrative="This is only a test.",
                placement=[
                    AddBannerAlert.Placement.CHART,
                    AddBannerAlert.Placement.APPOINTMENT_CARD,
                    AddBannerAlert.Placement.SCHEDULING_CARD,
                ],
                intent=AddBannerAlert.Intent.INFO,
                href="https://docs.canvasmedical.com",
            )
            if self.event.type in [EventType.PLUGIN_CREATED, EventType.PLUGIN_UPDATED]:
                banner.patient_filter = {"active": True}
            else:
                banner.patient_id = self.target
            return [banner.apply()]
    ```
###  Placement 
This determines where the banner alert appears.
####  `Placement.CHART`
This will place the banner under the patient's name on their chart
![](/assets/images/sdk/banner-alerts/banner_alert_placement_chart.png)
####  `Placement.TIMELINE`
This will place the banner on the top of the patient's timeline of notes in their chart
![](/assets/images/sdk/banner-alerts/banner_alert_placement_timeline.png)
####  `Placement.APPOINTMENT_CARD`
This will appear when you click an appointment on the calendar view
![](/assets/images/sdk/banner-alerts/banner_alert_placement_appointment_card.png)
####  `Placement.SCHEDULING_CARD`
This will appear when you select a patient during the scheduling of an appointment on the calendar view
![](/assets/images/sdk/banner-alerts/banner_alert_placement_scheduling_card.png)
####  `Placement.PROFILE`
This will place the banner under the patient's name on their patient registration page
![](/assets/images/sdk/banner-alerts/banner_alert_placement_profile.png)
###  Intent 
The type or severity of an alert. This will change the appearance of the banner alert.
####  `Intent.INFO`
![](/assets/images/sdk/banner-alerts/banner_alert_intent_info.png)
####  `Intent.WARNING`
![](/assets/images/sdk/banner-alerts/banner_alert_intent_warning.png)
####  `Intent.ALERT`
![](/assets/images/sdk/banner-alerts/banner_alert_intent_alert.png)
##  Removing a Banner Alert 
Removing a banner alert is done wih the `RemoveBannerAlert` class. Create an instance of the class, identifying the key of the alert and the patient id. Return the Effect by calling the `.apply()` method. Both the `key` and `patient_id` attributes are required.
    ```python
    from canvas_sdk.effects.banner_alert import RemoveBannerAlert
    banner_alert = RemoveBannerAlert(
        key='test-alert',
        patient_id="d4c933fe8f6948f6a7d2a42a2641b13b",
    )
    banner_alert.apply()
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/effect-banner-alerts/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-batch-originate/
##  Overview 
The `BatchOriginateCommandEffect` provides an efficient way to insert multiple commands into a note simultaneously. When you need to create many commands at once, using batch originate significantly improves performance compared to individual originate operations.
**Parameters:**
Attribute | Type | Required | Description  
---|---|---|---  
`commands` | `list` | `true` | List of command instances to batch originate  
`line_number` | `int` | `false` | Which note line the commands land on. Defaults to `-1`, which inserts them at the bottom of the note; set a specific line to target that line instead. Combine with `replace_line=True` to also take over (replace the content of) that line.  
`replace_line` | `bool` | `false` | Replace the content of the target line (the one set by `line_number`) with the originated commands, instead of inserting them as new lines. Defaults to `False`.  
**Returns:**
An `Effect` that can be applied to originate all commands in a single operation.
##  How It Works 
The batch originate effect processes multiple commands in a single operation:
  1. **Command Preparation** : Each command in the list required all necessary fields for `originate`
  2. **Note Update** : The note is updated once with all command UUIDs, rather than updating for each command individually
This approach minimizes database round-trips and improves overall performance.
##  Commit behavior 
`BatchOriginateCommandEffect` originates commands in the **uncommitted (draft)** state only. The batch effect has no `commit` option — every command in the batch is inserted into the note body as a draft.
Batch originating commands in a committed state is **not supported** , by design. The performance benefit of batching comes from collapsing the note update for many draft insertions into a single operation, and committing is a separate, per-command action with no equivalent batch saving.
Whenever a plugin needs to originate more than one command — whether you want them left as drafts or committed — batch origination is the right tool. To end up with committed commands, batch originate the drafts first so the note is updated once, then commit each command individually. Assign each command a `command_uuid` up front so it can be committed after it is originated:
    ```python
    from uuid import uuid4
    # Set command_uuid so each draft can be committed after batch origination
    plan1.command_uuid = str(uuid4())
    diagnose.command_uuid = str(uuid4())
    # One note update for all drafts, followed by a commit per command
    return [
        BatchOriginateCommandEffect(commands=[plan1, diagnose]).apply(),
        plan1.commit(),
        diagnose.commit(),
    ]
    ```
For three commands this performs three originates, **one** note update, and three commits. Collapsing the draft insertions into a single note update is where the performance benefit comes from.
##  Note body automations 
A [note body automation](/sdk/handlers-action-buttons/) is an entry a plugin adds to the note body's "/" command list. When a clinician selects the entry, the automation's `handle()` returns a `BatchOriginateCommandEffect` with `replace_line=True`. In this flow Canvas's note body "/" handling supplies the trigger-line position, so Canvas places the originated commands on the line the clinician typed the trigger on and replaces that line, rather than appending them to the note. The automation doesn't set `line_number` itself. If a plugin omits `replace_line`, it keeps its default of `False`, and the batch follows the effect's normal defaults: the originated commands insert at the bottom of the note (the `line_number=-1` default) rather than taking over the trigger line.
    ```python
    return [
        BatchOriginateCommandEffect(
            commands=[plan],
            replace_line=True,
        ).apply()
    ]
    ```
##  Basic Usage 
    ```python
    from canvas_sdk.commands import (
        PlanCommand,
        HistoryOfPresentIllnessCommand,
        QuestionnaireCommand,
        DiagnoseCommand
    )
    from canvas_sdk.effects.batch_originate import BatchOriginateCommandEffect
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.v1.data import Questionnaire, Note
    from canvas_sdk.events import EventType
    class Handler(BaseHandler):
        def compute(self):
            note_uuid = str(Note.objects.last().id)
            # Create multiple commands
            plan1 = PlanCommand()
            plan1.narrative = "Order labs for lipid panel"
            plan1.note_uuid = note_uuid
            plan2 = PlanCommand()
            plan2.narrative = "Schedule follow-up in 3 months"
            plan2.note_uuid = note_uuid
            hpi = HistoryOfPresentIllnessCommand()
            hpi.narrative = "Annual wellness visit"
            hpi.note_uuid = note_uuid
            diagnose = DiagnoseCommand()
            diagnose.icd10_code = "E11.9"
            diagnose.note_uuid = note_uuid
            diagnose.background = "Type 2 diabetes mellitus"
            # Add a questionnaire
            questionnaire = QuestionnaireCommand()
            questionnaire.note_uuid = note_uuid
            questionnaire_id = Questionnaire.objects.filter(
                name="Patient Health Questionnaire"
            ).first()
            if questionnaire_id:
                questionnaire.questionnaire_id = str(questionnaire_id.id)
            # Batch originate all commands
            commands_to_originate = [plan1, plan2, hpi, diagnose, questionnaire]
            return [BatchOriginateCommandEffect(commands=commands_to_originate).apply()]
    ```
##  Related Documentation 
  - [Commands Overview](/sdk/commands)
----- END PAGE https://docs.canvasmedical.com/sdk/effect-batch-originate/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-billing-line-items/
The Canvas SDK allows you to create, update, and remove Billing Line Items from the footer of a note.
##  Adding a Billing Line Item 
To add a billing line item to a note, import the `AddBillingLineItem` class, create an instance of it, and return the `.apply()` method from compute.
Attribute |  | Type | Description  
---|---|---|---  
note_id | required | String | The id of the [Note](/sdk/data-note/) where the line item should be associated.  
cpt | required | String | The billing code to use for the line item.  
units | optional | Integer | The number of units to bill for the code. Defaults to `1` if not provided.  
assessment_ids | optional | list[String] | List of Assessment ids from the note that are relevant to the code, also referred to as "diagnosis pointers".  
modifiers | optional | list[Coding] | The modifiers to create with the billing code.  
**Example:**
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.v1.data import Command, Assessment
    from canvas_sdk.effects.billing_line_item import AddBillingLineItem
    class MyHandler(BaseHandler):
        RESPONDS_TO = [
            EventType.Name(EventType.PERFORM_COMMAND__POST_ORIGINATE)
        ]
        def compute(self) -> list[Effect]:
            command_id = self.target
            command = Command.objects.get(id=command_id)
            note = command.note
            assessments = [
                str(i)
                for i in Assessment.objects.filter(note_id=note.dbid).values_list(
                    "id", flat=True
                )
            ]
            b = AddBillingLineItem(
                note_id=str(note.id),
                cpt="99213",
                units=1,
                assessment_ids=assessments,
                modifiers=[
                    {"code": "25", "system": "http://www.ama-assn.org/go/cpt"},
                    {"code": "59", "system": "http://www.ama-assn.org/go/cpt"},
                ],
            )
            return [b.apply()]
    ```
You don't set the line item's description in your plugin. When the line item is created, Canvas matches the `cpt` to a [ChargeDescriptionMaster](/sdk/data-charge-description-master/) charge and populates the description from that charge's `short_name`, truncated to 255 characters. If no charge matches the `cpt`, the description is left empty.
##  Updating a Billing Line Item 
To update a billing line item to a note, import the `UpdateBillingLineItem` class, create an instance of it, and return the `.apply()` method from compute.
Attribute |  | Type | Description  
---|---|---|---  
billing_line_item_id | required | String | The id of the [BillingLineItem](/sdk/data-billing-line-item/) to update.  
cpt | optional | String | The billing code to use for the line item.  
units | optional | Integer | The number of units to bill for the code.  
assessment_ids | optional | list[String] | List of Assessment ids from the note that are relevant to the code, also referred to as "diagnosis pointers".  
modifiers | optional | list[Coding] | The modifiers to create with the billing code.  
**Example:**
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.v1.data import Assessment, Command, BillingLineItem
    from canvas_sdk.effects.billing_line_item import UpdateBillingLineItem
    class MyHandler(BaseHandler):
        RESPONDS_TO = [
            EventType.Name(EventType.PERFORM_COMMAND__POST_COMMIT)
        ]
        def compute(self) -> list[Effect]:
            command_id = self.target
            command = Command.objects.get(id=command_id)
            note = command.note
            cpt = command.data["perform"]["value"]
            b_ids = BillingLineItem.objects.filter(cpt="99213", note=note).values_list(
                "id", flat=True
            )
            assessment = Assessment.objects.filter(note_id=note.dbid).first()
            updates = [
                UpdateBillingLineItem(
                    billing_line_item_id=str(b_id),
                    cpt=cpt,
                    units=1,
                    assessment_ids=[str(assessment.id)],
                    modifiers=[{"code": "47", "system": "http://www.ama-assn.org/go/cpt"}],
                )
                for b_id in b_ids
            ]
            return [update.apply() for update in updates]
    ```
##  Removing a Billing Line Item 
To remove a billing line item to a note, import the `RemoveBillingLineItem` class, create an instance of it, and return the `.apply()` method from compute.
Attribute |  | Type | Description  
---|---|---|---  
billing_line_item_id | required | String | The id of the [BillingLineItem](/sdk/data-billing-line-item/) to update.  
|  |  |   
**Example:**
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.v1.data import Command, BillingLineItem
    from canvas_sdk.effects.billing_line_item import RemoveBillingLineItem
    class MyHandler(BaseHandler):
        RESPONDS_TO = [
            EventType.Name(EventType.PERFORM_COMMAND__POST_ENTER_IN_ERROR)
        ]
        def compute(self) -> list[Effect]:
            command_id = self.target
            command = Command.objects.get(id=command_id)
            cpt = command.data["perform"]["value"]
            note_id = command.note.dbid
            b_ids = BillingLineItem.objects.filter(cpt=cpt, note_id=note_id).values_list(
                "id", flat=True
            )
            return [
                RemoveBillingLineItem(billing_line_item_id=str(b_id)).apply()
                for b_id in b_ids
            ]
    ```
For more information about the BillingLineItem data class, check out [this page](/sdk/data-billing-line-item).
----- END PAGE https://docs.canvasmedical.com/sdk/effect-billing-line-items/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-claims/
The Canvas SDK provides effects to facilitate managing claims. The `ClaimEffect` class provides a unified interface for:
  - adding labels to claims
  - removing labels from claims
  - moving claim to a queue
  - adding comments to claims
  - posting payments to claims
  - upserting metadata on claims
  - adding banner alerts to claims
  - removing banner alerts from claims
  - updating provider information on claims
  - updating the supervising provider on claims
  - setting the incident-to flag on claims
Additionally, the SDK provides a separate effect to update claim line items.
The following standalone effect classes are deprecated and will be removed in a future release. Please use the `ClaimEffect` class instead.
Deprecated Class | Old Import Path | New Equivalent  
---|---|---  
`AddClaimLabel` | `canvas_sdk.effects.claim_label` | `ClaimEffect.add_labels()`  
`RemoveClaimLabel` | `canvas_sdk.effects.claim_label` | `ClaimEffect.remove_labels()`  
`MoveClaimToQueue` | `canvas_sdk.effects.claim_queue` | `ClaimEffect.move_to_queue()`  
`AddClaimComment` | `canvas_sdk.effects.claim_comment` | `ClaimEffect.add_comment()`  
`PostClaimPayment` | `canvas_sdk.effects.payment` | `ClaimEffect.post_payment()`  
##  Claim Effect 
The `ClaimEffect` class facilitates operations on existing claims.
`from canvas_sdk.effects.claim import ClaimEffect`
###  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`claim_id` | `UUID` or `str` | Identifier for the claim | Yes  
###  Add Labels 
`ClaimEffect.add_labels()`: adds one or more labels to a claim, and optionally creates new labels before assigning them to the claim.
####  Parameters 
Parameter | Type | Description | Required  
---|---|---|---  
`labels` | `list[str or Label]` | List of label names and Label dataclasses* to apply to the claim | Yes  
*Labels can be passed in by name or as a Label dataclass. If the label with the provided name or values does not exist in your Canvas instance, it will be created and then applied to the specified claim. However, if a label already exists with the provided name or properties, it will add this existing label to the claim.
####  Label 
The `Label` dataclass represents a label with specific properties, including color and name.
Attribute | Type | Description | Required  
---|---|---|---  
`color` | [ColorEnum](/sdk/data-enumeration-types/#colorenum) | The color of the label in the UI | Yes  
`name` | `str` | The display name of the label | Yes  
####  Implementation Details 
  - Validates `claim_id` is provided and that the associated claim exists.
  - Validates that `labels` are provided and non-empty.
####  Example Usage 
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.effects.claim import ClaimEffect, Label
    from canvas_sdk.v1.data import Note
    from canvas_sdk.v1.data.common import ColorEnum
    class MyHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.NOTE_STATE_CHANGE_EVENT_CREATED)
        def compute(self) -> list[Effect]:
            """Creates and adds a new label the claim when charges are pushed.
            Adds the existing Urgent label when the note is locked."""
            note = Note.objects.get(id=self.event.context["note_id"])
            claim = note.get_claim()
            state = self.event.context["state"]
            if state == "PSH":
                claim_effect = ClaimEffect(claim_id=claim.id)
                return [claim_effect.add_labels([Label(color=ColorEnum.PINK, name="pushed not locked")])]
            elif state == "LKD":
                claim_effect = ClaimEffect(claim_id=claim.id)
                return [claim_effect.add_labels(["Urgent"])]
            return []
    ```
###  Remove Labels 
`ClaimEffect.remove_labels()`: removes existing labels from a claim.
####  Parameters 
Parameter | Type | Description | Required  
---|---|---|---  
`labels` | `list[str]` | List of label names to remove from the claim | Yes  
####  Implementation Details 
  - Validates `claim_id` is provided and that the associated claim exists
  - Validates `labels` is provided and non-empty
####  Example Usage 
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.effects.claim import ClaimEffect
    from canvas_sdk.v1.data import Note
    class MyHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.NOTE_STATE_CHANGE_EVENT_CREATED)
        def compute(self) -> list[Effect]:
            """When note is locked, remove the 'pushed not locked' label from the claim."""
            note = Note.objects.get(id=self.event.context["note_id"])
            claim = note.get_claim()
            state = self.event.context["state"]
            if state == "LKD":
                claim_effect = ClaimEffect(claim_id=claim.id)
                return [claim_effect.remove_labels(["pushed not locked"])]
            return []
    ```
###  Move to Queue 
`ClaimEffect.move_to_queue()`: moves a claim to a specific queue.
####  Parameters 
Parameter | Type | Description | Required  
---|---|---|---  
`queue` | `str` | The name of the queue to move the claim to, which must be a [valid name](/sdk/data-claim/#claimqueues) | Yes  
####  Implementation Details 
  - Validates `claim_id` is provided and that the associated claim exists
  - Validates `queue` is provided and the [queue with that name exists](/sdk/data-claim/#claimqueues)
####  Example Usage 
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.effects.claim import ClaimEffect
    from canvas_sdk.v1.data import Note
    class MyHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.NOTE_STATE_CHANGE_EVENT_CREATED)
        def compute(self) -> list[Effect]:
            if self.event.context["state"] == "ULK":
                note = Note.objects.get(id=self.event.context["note_id"])
                claim = note.get_claim()
                claim_effect = ClaimEffect(claim_id=str(claim.id))
                return [claim_effect.move_to_queue("NeedsClinicianReview")]
            return []
    ```
###  Add Comment 
`ClaimEffect.add_comment()`: creates a new comment on a claim.
####  Parameters 
Parameter | Type | Description | Required  
---|---|---|---  
`comment` | `str` | The comment text to add | Yes  
####  Implementation Details 
  - Validates `claim_id` is provided and that the associated claim exists
####  Example Usage 
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.effects.claim import ClaimEffect
    from canvas_sdk.v1.data import Patient, Claim
    class MyHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.COVERAGE_CREATED)
        def compute(self) -> list[Effect]:
            pt = Patient.objects.get(id=self.event.context["patient"]["id"])
            # patient's claims that have not been submitted yet
            pt_claims = Claim.objects.filter(
                note__patient=pt, current_queue__queue_sort_ordering__in=[1, 2, 3, 4]
            )
            return [
                ClaimEffect(claim_id=claim.id).add_comment(
                    "Patient has a new coverage, please confirm if this claim's coverage info should be updated."
                )
                for claim in pt_claims
            ]
    ```
###  Post Payment 
`ClaimEffect.post_payment()`: posts a payment to a claim, specifying payment details and line item transactions. This method supports payments from insurance or patient and allows you to specify payments, adjustments, transfers, and write-offs on individual claim line items.
####  Parameters 
Parameter | Type | Description | Required  
---|---|---|---  
`claim_coverage_id` | `UUID`, `str`, or `'patient'` | Identifier for the coverage or the string `'patient'` for patient payments. | Yes  
`line_item_transactions` | `list[LineItemTransaction]` | List of LineItemTransactions for claim line items. | Yes  
`method` | `PaymentMethod` | The PaymentMethod used (e.g., `cash`, `check`, `card`, `other`). | Yes  
`move_to_queue_name` | `str` | Name of the queue to move the claim to after payment. | No  
`claim_description` | `str` | Description for the claim allocation. | No  
`check_date` | `date` | Date of the check (required if method is `check`). | No  
`check_number` | `str` | Check number (required if method is `check`). | No  
`deposit_date` | `date` | Date the payment was deposited. | No  
`payment_description` | `str` | Description of the payment. | No  
####  Validations and Implementation Details 
  - `check_number` and `check_date` are required if payment method is `check`
  - `claim_id` must correspond to a valid existing claim. For insurance payments, there are a few ways to help you identify the correct claim using the [Claim](/sdk/data-claim/#claim), [ClaimSubmission](/sdk/data-claim/#claimsubmission), [ClaimCoverage](/sdk/data-claim/#claimcoverage) data models: 
    - `Claim.account_number` is the identifier that Canvas sends to the clearinghouse as a unique Canvas identifier for the claim.
    - `ClaimSubmission.clearinghouse_claim_id` is the identifier that the clearinghouse sends back to Canvas after they have accepted the claim, and is used for the clearinghouse's internal tracking of the claim.
    - `ClaimCoverage.payer_icn` is the identifier that the insurance company uses for their internal tracking of the claim, and is usually provided to Canvas via the clearinghouse.
  - `claim_coverage_id` must be either the string `"patient"` or correspond to a valid and **active** [ClaimCoverage](/sdk/data-claim/#claimcoverage) for the Claim. 
    - A helpful way to identify the correct claim coverage is to use the method `get_coverage_by_payer_id(payer_id: str, subscriber_number: str | None = None)` on the [Claim](/sdk/data-claim/#claim) data model, where `payer_id` is the standard id for the insurance company. You can optionally provide `subscriber_number` if it's possible that the patient has multiple coverages from the same payer and you want to identify the correct coverage.
  - `move_to_queue_name` must be a valid label from [ClaimQueue](/sdk/data-claim/#claimqueues), but is not required. If provided, the claim will move to this queue after payment is applied.
####  LineItemTransaction 
Attribute | Type | Description | Required  
---|---|---|---  
`claim_line_item_id` | `UUID` or `str` | Identifier for the claim line item. | Yes  
`charged` | `Decimal` | Charged amount for the line item. | No  
`allowed` | `Decimal` | Allowed amount for the line item. | No  
`payment` | `Decimal` | Payment amount for the line item. | No  
`adjustment` | `Decimal` | Adjustment amount for the line item. | No  
`adjustment_code` | `str` | Code describing the adjustment. | No  
`transfer_remaining_balance_to` | `UUID`, `str`, or `'patient'` | Transfer remaining balance to another payer or patient. | No  
`write_off` | `bool` | Whether to write off the remaining balance. | No  
#####  LineItemTransaction Validations 
  - `claim_line_item_id` must be a valid and **active** line item for the claim. It is recommended to search for it using `.active()` and by `proc_code`, e.g. `claim.line_items.active().filter(proc_code="99215").first()`
  - There can be many LineItemTransactions for the same `claim_line_item_id`, but the first LineItemTransaction for a claim line item must specify either a payment or an adjustment (or allowed amount); subsequent transactions require an adjustment.
  - If an `adjustment` is specified, an `adjustment_code` must also be provided.
  - If the adjustment code is for a transfer (code starts with "Transfer"), a valid `transfer_remaining_balance_to` must be provided, and it cannot be the same payer as the `claim_coverage_id` payer.
  - `transfer_remaining_balance_to` can only be made to the patient (using the string `"patient"`) or to an **active** `claim_coverage_id` for the claim.
  - Adjustments cannot simultaneously write off and transfer the same amount; only one of `write_off` or `transfer_remaining_balance_to` should be set on LineItemTransactions where `adjustment` is present.
  - Adjustments and transfers are not allowed for COPAY charges, i.e. claim line items where the proc_code = `COPAY`. Only payments are allowed for those line items.
  - `payment` on COPAY line items must have a `claim_coverage_id` equal to `"patient"`.
  - `allowed` should be empty or $0 if `claim_coverage_id` is equal to `"patient"`.
####  PaymentMethod Enumeration Type 
Enum | Value  
---|---  
`CASH` | cash  
`CHECK` | check  
`CARD` | card  
`OTHER` | other  
####  Example Usage 
The most common use case for this method will be with the [SimpleAPI](/sdk/handlers-simple-api-http/) handler.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.v1.data import ClaimLineItem, Claim
    from decimal import Decimal
    from canvas_sdk.effects.claim import (
        ClaimEffect,
        PaymentMethod,
        LineItemTransaction,
    )
    from datetime import date
    from canvas_sdk.effects.simple_api import JSONResponse, Response
    from canvas_sdk.handlers.simple_api import APIKeyCredentials, SimpleAPIRoute
    class MyAPI(SimpleAPIRoute):
        PATH = "/routes/post-claim-payment"
        def authenticate(self, credentials: APIKeyCredentials) -> bool:
            # replace with desired authentication logic
            return True
        def get_claim_line_item(self, claim: Claim, proc_code: str) -> ClaimLineItem | None:
            return claim.line_items.active().filter(proc_code=proc_code).first()
        def create_line_item_transactions(
            self, charge: dict, claim: Claim, next_coverage_id: str
        ) -> list[LineItemTransaction]:
            transactions = []
            if not (line_item := self.get_claim_line_item(claim, charge.get("proc_code"))):
                return transactions
            charged = Decimal(charge["charge"])
            payment = Decimal(charge["paid"])
            allowed = Decimal(charge["allowed"])
            adjustments = charge.get("adjustment", [])
            first_adjustment = adjustments[0]
            payment = LineItemTransaction(
                claim_line_item_id=line_item.id,
                charged=charged,
                payment=payment,
                allowed=allowed,
                adjustment=Decimal(first_adjustment["amount"]),
                adjustment_code=f"{first_adjustment['group']}-{first_adjustment['code']}",
                # replace with whatever logic needed for resolving remaining balance
                transfer_remaining_balance_to="patient"
                if first_adjustment["group"] == "PR"
                else next_coverage_id,
            )
            transactions.append(payment)
            additional_adjustments = adjustments[1:]
            for adj in additional_adjustments:
                transaction = LineItemTransaction(
                    claim_line_item_id=line_item.id,
                    adjustment=Decimal(adj["amount"]),
                    adjustment_code=f"{adj['group']}-{adj['code']}",
                    # replace with whatever logic needed for resolving remaining balance
                    transfer_remaining_balance_to="patient"
                    if adj["group"] == "PR"
                    else next_coverage_id,
                )
                transactions.append(transaction)
            return transactions
        def get_claim(
            self, account_number: str, clearinghouse_claim_id: str
        ) -> Claim | None:
            return (
                Claim.objects.filter(account_number=account_number).first()
                or Claim.objects.filter(
                    submissions__clearinghouse_claim_id=clearinghouse_claim_id,
                ).first()
            )
        def post_payment(
            self,
            claim_payment_info: dict,
            check_number: str,
            check_date: str,
            payer_id: str,
        ) -> Effect | None:
            account_number = claim_payment_info.get("pcn")
            clearinghouse_claim_id = claim_payment_info.get("payer_icn")
            if not (claim := self.get_claim(account_number, clearinghouse_claim_id)):
                return None
            insurance_number = claim_payment_info.get("ins_number")
            if not (coverage := claim.get_coverage_by_payer_id(payer_id, insurance_number)):
                return None
            next_coverage_id = (
                claim.coverages.active().exclude(payer_id=payer_id).first().id
            )
            line_item_transactions = []
            for c in claim_payment_info.get("charge", []):
                line_item_transactions.extend(
                    self.create_line_item_transactions(c, claim, next_coverage_id)
                )
            claim_effect = ClaimEffect(claim_id=claim.id)
            return claim_effect.post_payment(
                claim_coverage_id=coverage.id,
                line_item_transactions=line_item_transactions,
                method=PaymentMethod.CHECK,
                check_date=date.fromisoformat(check_date),
                check_number=check_number,
                deposit_date=date.fromisoformat(check_date),
                payment_description="Aetna 835 payment",
                claim_description="Payment applied via 835",
            )
        def post(self) -> list[Response | Effect]:
            payment_info = self.request.json()
            check_number = payment_info.get("check_number")
            check_date = payment_info.get("paid_date")
            payer_id = payment_info.get("payerid")
            payments = [
                p
                for claim in payment_info.get("claim", [])
                if (p := self.post_payment(claim, check_number, check_date, payer_id))
            ]
            return payments + [JSONResponse({"message": "ok"})]
    ```
With the above plugin installed, an example call to the endpoint would look like this:
    ```bash
    curl -X POST "http://localhost:8000/plugin-io/api/pmt/routes/post-claim-payment" \
      -H "Content-Type: application/json" \
      -H "Authorization: <api-key>" \
      -d '{
        "paid_date": "2025-11-06",
        "eraid": "23853671",
        "check_number": "397547083-1662491258",
        "paid_amount": "346.00",
        "payerid": "60054",
        "claim": [
            {
                "pcn": "124974-1",
                "payer_icn": "TST397547083",
                "total_charge": "48",
                "from_dos": "20250827",
                "pat_name_f": "ETHYL",
                "ins_name_l": "BATES",
                "total_paid": "0",
                "thru_dos": null,
                "pat_name_l": "BATES",
                "ins_number": "412098745",
                "ins_name_f": "NORMAN",
                "charge": [
                    {
                        "chgid": "221043771",
                        "from_dos": "20220827",
                        "adjustment": [{"amount": "48", "group": "OA", "code": "109"}],
                        "paid": "0",
                        "allowed": "0",
                        "proc_code": "99212",
                        "charge": "48",
                        "thru_dos": null,
                        "units": "1"
                    }
                ]
            },
            {
                "pcn": "21830-1",
                "payer_icn": "TST397547094",
                "total_charge": "75",
                "from_dos": "20220827",
                "pat_name_f": "MARYLOU",
                "ins_name_l": "DENNIS",
                "total_paid": "45",
                "thru_dos": null,
                "pat_name_l": "DENNIS",
                "ins_number": "223444467",
                "ins_name_f": "ROBERT",
                "charge": [
                    {
                        "chgid": "221043716",
                        "from_dos": "20220827",
                        "adjustment": [
                            {"amount": "15", "group": "CO", "code": "45"},
                            {"amount": "10", "group": "PR", "code": "2"},
                            {"amount": "5", "group": "PR", "code": "3"}
                        ],
                        "paid": "45",
                        "allowed": "60",
                        "proc_code": "99213",
                        "charge": "75",
                        "thru_dos": null,
                        "units": "1"
                    }
                ]
            }
        ]
    }'
    ```
###  Upsert Metadata 
`ClaimEffect.upsert_metadata()`: upserts a key-value metadata record on a claim. If a metadata record with the given key already exists for the claim, its value will be updated. Otherwise, a new metadata record will be created.
####  Parameters 
Parameter | Type | Description | Required  
---|---|---|---  
`key` | `str` | The key of the metadata | Yes  
`value` | `str` | The value of the metadata | Yes  
####  Implementation Details 
  - Validates `claim_id` is provided and that the associated claim exists
  - The claim-key pair is unique; upserting with an existing key will update the value rather than creating a duplicate
  - If a metadata record already exists with the same claim, key, and value, no update is performed
####  Example Usage 
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.effects.claim import ClaimEffect
    from canvas_sdk.v1.data import Note
    class MyHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.NOTE_STATE_CHANGE_EVENT_CREATED)
        def compute(self) -> list[Effect]:
            """When a note is locked, store the lock timestamp as metadata on the claim."""
            note = Note.objects.get(id=self.event.context["note_id"])
            claim = note.get_claim()
            state = self.event.context["state"]
            if state == "LKD":
                claim_effect = ClaimEffect(claim_id=claim.id)
                return [claim_effect.upsert_metadata(key="locked_at", value=str(note.modified))]
            return []
    ```
###  Add Banner 
`ClaimEffect.add_banner()`: adds a banner alert to a claim. Banner alerts are displayed in the UI to surface important information about a claim.
####  Parameters 
Parameter | Type | Description | Required  
---|---|---|---  
`key` | `str` | A unique key identifying the banner alert | Yes  
`narrative` | `str` | The banner text to display (max 90 characters) | Yes  
`intent` | BannerAlertIntent | The visual intent/severity of the banner | Yes  
`href` | `str` | An optional link URL for the banner | No  
####  BannerAlertIntent Enumeration Type 
Enum | Value  
---|---  
`INFO` | info  
`WARNING` | warning  
`ALERT` | alert  
####  Implementation Details 
  - Validates `claim_id` is provided and that the associated claim exists
  - The `narrative` field has a maximum length of 90 characters
####  Example Usage 
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.effects.claim import ClaimEffect, BannerAlertIntent
    from canvas_sdk.v1.data import Note
    class MyHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.NOTE_STATE_CHANGE_EVENT_CREATED)
        def compute(self) -> list[Effect]:
            """When a note is unlocked, add a warning banner to the claim."""
            note = Note.objects.get(id=self.event.context["note_id"])
            claim = note.get_claim()
            state = self.event.context["state"]
            if state == "ULK":
                claim_effect = ClaimEffect(claim_id=claim.id)
                return [
                    claim_effect.add_banner(
                        key="review-needed",
                        narrative="This claim needs review before resubmission.",
                        intent=BannerAlertIntent.WARNING,
                    )
                ]
            return []
    ```
###  Remove Banner 
`ClaimEffect.remove_banner()`: removes a banner alert from a claim by its key.
####  Parameters 
Parameter | Type | Description | Required  
---|---|---|---  
`key` | `str` | The unique key of the banner alert to remove | Yes  
####  Implementation Details 
  - Validates `claim_id` is provided and that the associated claim exists
####  Example Usage 
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.effects.claim import ClaimEffect
    from canvas_sdk.v1.data import Note
    class MyHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.NOTE_STATE_CHANGE_EVENT_CREATED)
        def compute(self) -> list[Effect]:
            """When a note is locked, remove the review-needed banner from the claim."""
            note = Note.objects.get(id=self.event.context["note_id"])
            claim = note.get_claim()
            state = self.event.context["state"]
            if state == "LKD":
                claim_effect = ClaimEffect(claim_id=claim.id)
                return [claim_effect.remove_banner(key="review-needed")]
            return []
    ```
###  Update Provider 
`ClaimEffect.update_provider()`: updates provider information on a claim, including billing provider, rendering/attending provider, referring provider, ordering provider, and facility details. All parameters are optional — only the fields you provide will be updated.
####  Parameters 
Parameter | Type | Description | Required  
---|---|---|---  
`billing_provider` | ClaimBillingProvider or `None` | Billing provider information | No  
`provider` | ClaimProvider or `None` | Rendering or attending provider information | No  
`referring_provider` | ClaimReferringProvider or `None` | Referring provider information | No  
`ordering_provider` | ClaimOrderingProvider or `None` | Ordering provider information | No  
`facility` | ClaimFacility or `None` | Facility information | No  
####  ClaimBillingProvider 
Attribute | Type | Description  
---|---|---  
`name` | `str` or `None` | Provider name (max 255 chars)  
`phone` | `str` or `None` | Phone number (max 15 chars)  
`addr1` | `str` or `None` | Address line 1 (max 255 chars)  
`addr2` | `str` or `None` | Address line 2 (max 255 chars)  
`city` | `str` or `None` | City (max 255 chars)  
`state` | `str` or `None` | State code (max 2 chars)  
`zip` | `str` or `None` | ZIP code (max 255 chars)  
`npi` | `str` or `None` | NPI number (max 10 chars)  
`tax_id` | `str` or `None` | Tax ID (max 100 chars)  
`tax_id_type` | `str` or `None` | Tax ID type (max 1 char)  
`taxonomy` | `str` or `None` | Taxonomy code (max 100 chars)  
`clia_number` | `str` or `None` | CLIA number (max 100 chars)  
####  ClaimProvider 
Represents the rendering or attending provider.
Attribute | Type | Description  
---|---|---  
`first_name` | `str` or `None` | First name (max 255 chars)  
`last_name` | `str` or `None` | Last name (max 255 chars)  
`middle_name` | `str` or `None` | Middle name (max 255 chars)  
`npi` | `str` or `None` | NPI number (max 10 chars)  
`tax_id` | `str` or `None` | Tax ID (max 100 chars)  
`tax_id_type` | `str` or `None` | Tax ID type (max 1 char)  
`taxonomy` | `str` or `None` | Taxonomy code (max 100 chars)  
`ptan_identifier` | `str` or `None` | PTAN identifier (max 50 chars)  
`addr1` | `str` or `None` | Address line 1 (max 255 chars)  
`addr2` | `str` or `None` | Address line 2 (max 255 chars)  
`city` | `str` or `None` | City (max 255 chars)  
`state` | `str` or `None` | State code (max 2 chars)  
`zip` | `str` or `None` | ZIP code (max 255 chars)  
####  ClaimReferringProvider 
Attribute | Type | Description  
---|---|---  
`first_name` | `str` or `None` | First name (max 255 chars)  
`last_name` | `str` or `None` | Last name (max 255 chars)  
`middle_name` | `str` or `None` | Middle name (max 255 chars)  
`npi` | `str` or `None` | NPI number (max 10 chars)  
`ptan_identifier` | `str` or `None` | PTAN identifier (max 50 chars)  
####  ClaimOrderingProvider 
Attribute | Type | Description  
---|---|---  
`first_name` | `str` or `None` | First name (max 255 chars)  
`last_name` | `str` or `None` | Last name (max 255 chars)  
`middle_name` | `str` or `None` | Middle name (max 255 chars)  
`npi` | `str` or `None` | NPI number (max 10 chars)  
####  ClaimFacility 
Attribute | Type | Description  
---|---|---  
`name` | `str` or `None` | Facility name (max 255 chars)  
`npi` | `str` or `None` | NPI number (max 10 chars)  
`addr1` | `str` or `None` | Address line 1 (max 255 chars)  
`addr2` | `str` or `None` | Address line 2 (max 255 chars)  
`city` | `str` or `None` | City (max 255 chars)  
`state` | `str` or `None` | State code (max 2 chars)  
`zip` | `str` or `None` | ZIP code (max 255 chars)  
`hosp_from_date` | `date` or `None` | Hospitalization start date  
`hosp_to_date` | `date` or `None` | Hospitalization end date  
####  Implementation Details 
  - Validates `claim_id` is provided and that the associated claim exists
  - Validates that the claim has existing provider information (i.e., the claim's provider record is populated)
  - Only fields with non-`None` values are included in the update — any fields left as `None` are excluded
  - Date fields (`hosp_from_date`, `hosp_to_date`) are serialized to ISO format strings
####  Example Usage 
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.v1.data import Claim, Note, PatientFacilityAddress
    from canvas_sdk.v1.data.common import AddressState
    from canvas_sdk.effects.claim.claim import ClaimEffect, ClaimBillingProvider, ClaimFacility
    class ClaimProviderHandler(BaseHandler):
        RESPONDS_TO = [
            EventType.Name(EventType.CLAIM_CREATED),
            EventType.Name(EventType.NOTE_STATE_CHANGE_EVENT_CREATED),
        ]
        def get_claim(self) -> Claim | None:
            if self.event.type == EventType.CLAIM_CREATED:
                return Claim.objects.get(id=self.event.target.id)
            if self.event.context["state"] not in ["LKD", "PSH", "DSC"]:
                # claim provider details can change when notes are locked, pushed, or discharged
                return None
            return Note.objects.get(self.event.target.id).get_claim()
        def get_patient_facility(self, claim) -> PatientFacilityAddress | None:
            return PatientFacilityAddress.objects.filter(
                patient=claim.note.patient, state=AddressState.ACTIVE
            ).first()
        def compute(self) -> list[Effect]:
            """When a claim is created, or note is locked/pushed/charged, update the claim's provider information."""
            if not (claim := self.get_claim()):
                return []
            if not (facility := self.get_patient_facility(claim)):
                return []
            billing = ClaimBillingProvider(
                name=facility.facility.name,
                phone=facility.facility.phone_number,
                addr1=facility.line1,
                addr2=facility.line2,
                city=facility.city,
                state=facility.state_code,
                zip=facility.postal_code,
                npi=facility.facility.npi_number,
            )
            facility = ClaimFacility(
                name=facility.facility.name,
                npi=facility.facility.npi_number,
                addr1=facility.line1,
                addr2=facility.line2,
                city=facility.city,
                state=facility.state_code,
                zip=facility.postal_code,
            )
            return [
                ClaimEffect(claim_id=claim.id).update_provider(
                    billing_provider=billing, facility=facility
                )
            ]
    ```
###  Update Supervising Provider 
`ClaimEffect.update_supervising_provider()`: sets the supervising provider snapshot on a claim. This snapshot is captured for billing purposes (837P loop 2310D and the printed CMS-1500 form) and remains frozen after submission.
Provide exactly one of:
  - A `staff_id` to populate the snapshot from an existing Staff record (name, NPI, taxonomy, tax ID). The snapshot remains linked to the Staff record.
  - A `ClaimSupervisingProvider` dataclass to specify the snapshot fields directly. This clears any Staff association.
####  Parameters 
Parameter | Type | Description | Required  
---|---|---|---  
`supervising_provider` | ClaimSupervisingProvider or `None` | Free-text provider snapshot | Yes (if `staff_id` not given)  
`staff_id` | `str` or `None` | Staff identifier to populate from | Yes (if `supervising_provider` not given)  
####  ClaimSupervisingProvider 
The `ClaimSupervisingProvider` dataclass represents a supervising provider's identifying information for billing purposes.
Attribute | Type | Description  
---|---|---  
`first_name` | `str` or `None` | First name (max 255 chars)  
`last_name` | `str` or `None` | Last name (max 255 chars)  
`middle_name` | `str` or `None` | Middle name (max 255 chars)  
`npi` | `str` or `None` | NPI number (max 10 chars)  
`taxonomy` | `str` or `None` | Taxonomy code (max 100 chars)  
`tax_id` | `str` or `None` | Tax ID (max 100 chars)  
`tax_id_type` | `str` or `None` | Tax ID type (max 1 char)  
####  Implementation Details 
  - Validates `claim_id` is provided and that the associated claim exists
  - Validates that exactly one of `staff_id` or `supervising_provider` is provided
  - If `staff_id` is provided, validates that the Staff record exists
####  Example Usage 
Using a Staff record:
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.effects.claim import ClaimEffect
    from canvas_sdk.v1.data import Note
    class SupervisingProviderHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.NOTE_STATE_CHANGE_EVENT_CREATED)
        def compute(self) -> list[Effect]:
            """When a note is locked, set the supervising provider from the note's supervising provider."""
            note = Note.objects.get(id=self.event.context["note_id"])
            claim = note.get_claim()
            state = self.event.context["state"]
            if state == "LKD" and note.supervising_provider:
                claim_effect = ClaimEffect(claim_id=claim.id)
                return [claim_effect.update_supervising_provider(staff_id=str(note.supervising_provider.id))]
            return []
    ```
Using free-text provider information:
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.effects.claim import ClaimEffect, ClaimSupervisingProvider
    from canvas_sdk.v1.data import Note
    class SupervisingProviderHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.NOTE_STATE_CHANGE_EVENT_CREATED)
        def compute(self) -> list[Effect]:
            """When a note is locked, set a custom supervising provider on the claim."""
            note = Note.objects.get(id=self.event.context["note_id"])
            claim = note.get_claim()
            state = self.event.context["state"]
            if state == "LKD":
                claim_effect = ClaimEffect(claim_id=claim.id)
                return [
                    claim_effect.update_supervising_provider(
                        ClaimSupervisingProvider(
                            first_name="Jane",
                            last_name="Doe",
                            npi="1234567890",
                            taxonomy="207Q00000X",
                        )
                    )
                ]
            return []
    ```
###  Set Incident To 
`ClaimEffect.set_incident_to()`: sets the `incident_to` billing flag for Medicare incident-to billing. When set to `True`, the claim's rendering provider fields (name, NPI, taxonomy) are automatically replaced with the supervising provider's details.
####  Parameters 
Parameter | Type | Description | Required  
---|---|---|---  
`value` | `bool` | Whether the claim is billed incident-to the supervising physician | Yes  
####  How Incident-To Billing Works 
When a claim is marked as incident-to:
  1. **Automatic rendering provider swap** : The rendering provider fields (first name, last name, middle name, NPI, and taxonomy) are replaced with the supervising provider's values. This swap happens immediately when `incident_to` is set to `True`, not at claim submission.
  2. **Billing provider unchanged** : The billing provider information (NM1 _85 on the 837P) remains unchanged. Only the rendering provider (NM1_ 82 / Box 24J) is affected.
  3. **Frozen after submission** : Once a claim has been submitted to the clearinghouse, the rendering provider swap no longer occurs even if incident-to settings change.
  4. **Re-sync on supervising provider change** : If the supervising provider is updated on an incident-to claim, the rendering provider fields are automatically re-synced to match the new supervising provider.
####  Printed CMS-1500 Form 
The supervising provider appears on the printed CMS-1500 (HCFA) form differently depending on whether the claim is marked incident-to:
Scenario | Box 24J (Rendering NPI) | Box 17 (Referring/Supervising Provider)  
---|---|---  
**Incident-to claim** | Supervising provider's NPI (via the automatic rendering swap) | Not populated for supervising—the provider already appears in Box 24J  
**Non-incident-to claim with supervising provider** | Original rendering provider's NPI | Supervising provider with **DQ** qualifier (if no referring or ordering provider exists)  
For non-incident-to claims with a supervising provider who has a valid NPI and no referring or ordering provider, the printed form populates Box 17 with the supervising provider's name, Box 17a with the "DQ" (supervising physician) qualifier, and Box 17b with the supervising provider's NPI.
Box 17 follows a priority order: referring provider (DN) takes precedence over ordering provider (DK), which takes precedence over supervising provider (DQ). The supervising provider only appears in Box 17 when no referring or ordering provider is present.
####  Claim Errors 
The following errors prevent claim submission when incident-to is enabled:
Error | Description | Solution  
---|---|---  
Missing supervising provider | The claim is marked incident-to but has no supervising provider with an NPI. | Add a supervising provider with a valid NPI, or disable incident-to.  
Supervising provider same as rendering | The supervising provider is the same as the note's original rendering provider. | Set the supervising provider to a different physician, or disable incident-to.  
####  Claim Warnings 
The following warnings are displayed for incident-to claims but do not prevent submission:
Warning | Description | Guidance  
---|---|---  
Non-office place of service | The place of service is not office (11). Incident-to billing is generally not valid in facility settings per 42 CFR 410.26. | Confirm the place of service is correct, or disable incident-to if it does not apply.  
Non-Medicare payer | The payer is not Medicare. Incident-to rules are a Medicare policy; coverage varies by commercial payer. | Confirm the payer accepts incident-to billing, or disable incident-to if it does not apply.  
####  Implementation Details 
  - Validates `claim_id` is provided and that the associated claim exists
  - The rendering provider swap requires a valid supervising provider with an NPI
  - The swap is skipped if the supervising provider's NPI is missing or invalid
####  Example Usage 
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.effects.claim import ClaimEffect
    from canvas_sdk.v1.data import Note
    class IncidentToHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.CLAIM_SUPERVISING_PROVIDER_CHANGED)
        def compute(self) -> list[Effect]:
            """When a supervising provider is set on a claim, enable incident-to billing."""
            claim_id = self.event.target.id
            claim_effect = ClaimEffect(claim_id=claim_id)
            return [claim_effect.set_incident_to(True)]
    ```
* * *
##  UpdateClaimLineItem 
The `UpdateClaimLineItem` effect allows you to update the `charge` field and `linked_diagnosis_codes` on a specified claim line item.
###  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`claim_line_item_id` | `UUID` or `str` | Identifier for the claim line item | Yes  
`charge` | `float` | The charge amount to update on the claim line item | No  
`linked_diagnosis_codes` | `list[UUID or str]` | List of [ClaimLineItemDiagnosisCode](/sdk/data-claim/#claimlineitemdiagnosiscode) IDs to link to the claim line item | No  
###  Implementation Details 
  - Validates `claim_line_item_id` is provided and that the associated claim line item exists
  - If `linked_diagnosis_codes` is provided, validates that all [ClaimLineItemDiagnosisCode](/sdk/data-claim/#claimlineitemdiagnosiscode) IDs correspond to existing diagnosis codes on the claim line item
  - The `linked_diagnosis_codes` list represents the complete set of diagnosis codes that will be linked to the claim line item when the effect is applied. Any diagnosis codes not included in this list will be unlinked. If you wish to add a new code to the existing linked codes, you must first retrieve the current list and include all codes you want to remain linked: `list(claim_line_item.diagnosis_codes.filter(linked=True).values_list("id", flat=True)) + [new_code_id]`
###  Example Usage 
Updating charge amount.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.v1.data import Note, ClaimLineItem
    from canvas_sdk.effects.claim_line_item import UpdateClaimLineItem
    class MyHandler(BaseHandler):
        """When a note is unlocked, update the associated claim's line items to have a charge of $0.00.
        When a note is locked, update the associated claim's line items to have a charge of $500.00."""
        RESPONDS_TO = EventType.Name(EventType.NOTE_STATE_CHANGE_EVENT_CREATED)
        def get_line_items(self) -> ClaimLineItem:
            note = Note.objects.get(id=self.event.context["note_id"])
            claim = note.get_claim()
            return claim.get_active_claim_line_items()
        def update_charge(self, id: str, charge: float) -> Effect:
            return UpdateClaimLineItem(claim_line_item_id=id, charge=charge).apply()
        def update_all_items(self, charge: float) -> list[Effect]:
            return [self.update_charge(line_item.id, charge) for line_item in self.get_line_items()]
        def compute(self) -> list[Effect]:
            if self.event.context["state"] == "ULK":
                return self.update_all_items(0.00)
            if self.event.context["state"] == "LKD":
                return self.update_all_items(500.00)
            return []
    ```
Linking and un-linking diagnosis codes.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.v1.data import Note, ClaimLineItem
    from canvas_sdk.effects.claim_line_item import UpdateClaimLineItem
    class MyHandler(BaseHandler):
        RESPONDS_TO = [
            EventType.Name(EventType.NOTE_STATE_CHANGE_EVENT_CREATED),
        ]
        def compute(self) -> list[Effect]:
            effects = []
            note = Note.objects.get(id=self.event.context["note_id"])
            if not (claim := note.get_claim()):
                return effects
            state = self.event.context["state"]
            if state == "PSH":
                # only link proc codes starting with "99" to diag codes starting with "I"
                items = claim.line_items.filter(proc_code__startswith="99")
                return self.generate_effects(items, self.get_diags_that_start_with_I)
            if state == "LKD":
                # link all proc codes to all diag codes
                items = claim.line_items.all()
                return self.generate_effects(items, self.get_all_diags)
            if state == "ULK":
                # unlink proc codes starting with "99" from diag codes starting with "I"
                items = claim.line_items.filter(proc_code__startswith="99")
                return self.generate_effects(items, self.get_diags_that_dont_start_with_I)
            if state == "DLT":
                # unlink all proc codes from all diag codes
                items = claim.line_items.all()
                return self.generate_effects(items, self.get_no_diags)
            return effects
        def get_diags_that_start_with_I(self, item: ClaimLineItem) -> list[str]:
            return list(
                item.diagnosis_codes.filter(code__startswith="I").values_list(
                    "id", flat=True
                )
            )
        def get_diags_that_dont_start_with_I(self, item: ClaimLineItem) -> list[str]:
            return list(
                item.diagnosis_codes.exclude(code__startswith="I").values_list(
                    "id", flat=True
                )
            )
        def get_all_diags(self, item: ClaimLineItem) -> list[str]:
            return list(item.diagnosis_codes.values_list("id", flat=True))
        def get_no_diags(self, item: ClaimLineItem) -> list[str]:
            return []
        def generate_effects(self, items, get_diag_ids) -> list[Effect]:
            return [
                UpdateClaimLineItem(
                    claim_line_item_id=item.id, linked_diagnosis_codes=get_diag_ids(item)
                ).apply()
                for item in items
            ]
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/effect-claims/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-command-metadata/
The `upsert_metadata` method on any command class provides a flexible key-value storage system for command-specific data within the Canvas system. This method enables the creation and updating of custom metadata entries associated with command records, allowing for extensible command information storage beyond standard command fields.
##  Overview 
Command metadata serves as a powerful extension mechanism for storing custom command-related information that doesn't fit within the standard command data model. Metadata is managed through the `upsert_metadata` method available on all command effect classes.
Metadata can be written two ways, and they store to the same place:
  - **From your plugin** , with the `upsert_metadata` method documented on this page.
  - **From the note** , with the [Command Metadata Create form effect](/sdk/command-metadata-create-form-effect/), which displays additional fields alongside a command in the chart and stores whatever a user enters as metadata against that command.
Either way, the entries are readable as [CommandMetadata](/sdk/data-command/#commandmetadata) in the data module.
##  Method 
###  upsert_metadata(key: str, value: str) → Effect 
Creates or updates a metadata entry for the specified command.
####  Parameters 
Parameter | Type | Description | Required  
---|---|---|---  
`key` | `str` | Unique identifier for the metadata entry within the command context | Yes  
`value` | `str` | The metadata value to store | Yes  
####  Prerequisites 
The command effect must be initialized with a `command_uuid`. This can be either the UUID of an existing command or the UUID of a command being originated in the same effect list.
Attribute | Type | Description | Required  
---|---|---|---  
`command_uuid` | `str` | Id of the command record to associate metadata with | Yes  
####  Returns 
An `Effect` object configured for upserting command metadata.
####  Behavior 
  - If a metadata entry with the specified key already exists for the command, it will be updated with the new value
  - If no entry exists, a new metadata entry will be created
  - The operation is idempotent - repeated calls with the same key and value will not create duplicate entries
  - Raises `ValueError` if `command_uuid` is not set on the command effect
##  Implementation Details 
###  Validation 
The effect performs validation at two stages:
  1. **SDK Validation** : Ensures all required fields are provided before the effect is created
     - `command_uuid` must be set on the command effect
     - Both `key` and `value` must be provided
  2. **Server-Side Validation** : When the effect is processed, the server verifies that the referenced command exists
     - Returns a descriptive error if the command is not found after all effects in the list have been processed
##  Example Usage 
###  Basic Usage 
    ```python
    from canvas_sdk.commands import PlanCommand
    plan = PlanCommand(command_uuid="63hdik")
    effect = plan.upsert_metadata(key="my_plugin:priority", value="high")
    ```
###  Example: Chaining with originate() 
You can attach metadata to a command at the same time you originate it by returning both effects in the same list:
    ```python
    import uuid
    from canvas_sdk.commands import PlanCommand
    from canvas_sdk.effects import Effect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class OriginateWithMetadata(BaseHandler):
        """Originates a plan command with metadata attached in a single operation."""
        RESPONDS_TO = EventType.Name(EventType.PATIENT_CHART__SECTION__LOADED)
        def compute(self) -> list[Effect]:
            command_uuid = str(uuid.uuid4())
            plan = PlanCommand(
                note_uuid=self.context["note_id"],
                command_uuid=command_uuid,
                narrative="Follow up in 2 weeks",
            )
            return [
                plan.originate(),
                plan.upsert_metadata(key="my_plugin:source", value="auto_generated"),
            ]
    ```
###  Example: Tagging a command on commit 
    ```python
    from canvas_sdk.commands import PlanCommand
    from canvas_sdk.effects import Effect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class TagPlanOnCommit(BaseHandler):
        """Tags a plan command with a workflow stage when it is committed."""
        RESPONDS_TO = EventType.Name(EventType.PLAN_COMMAND__POST_COMMIT)
        def compute(self) -> list[Effect]:
            plan = PlanCommand(command_uuid=self.event.target.id)
            return [plan.upsert_metadata(key="my_plugin:workflow_stage", value="committed")]
    ```
###  Responding to metadata events 
Once metadata is upserted, `COMMAND_METADATA_CREATED` and `COMMAND_METADATA_UPDATED` events are emitted and can be handled by plugins:
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.v1.data.command import CommandMetadata
    from logger import log
    class CommandMetadataListener(BaseHandler):
        """Reacts to command metadata changes."""
        RESPONDS_TO = [
            EventType.Name(EventType.COMMAND_METADATA_CREATED),
            EventType.Name(EventType.COMMAND_METADATA_UPDATED),
        ]
        def compute(self) -> list[Effect]:
            metadata = CommandMetadata.objects.get(id=self.event.target.id)
            log.info(f"Command {metadata.command.id}: {metadata.key}={metadata.value}")
            return []
    ```
##  Best Practices 
###  Key Naming Conventions 
  1. **Use Descriptive Names** : Choose keys that clearly indicate the purpose of the metadata
     - Good: `workflow_stage`, `external_id`, `review_status`
     - Avoid: `data1`, `temp`, `misc`
  2. **Namespace Your Keys** : Prefix keys with your plugin name to avoid collisions with other plugins
     - Example: `my_plugin:workflow_stage`, `my_plugin:external_id`
###  Value Storage 
**String Serialization** : All values are stored as strings. For complex data types, serialize to JSON:
    ```python
       import json
       from canvas_sdk.commands import DiagnoseCommand
       cmd = DiagnoseCommand(command_uuid="abc123")
       data = {"reviewer": "user-id", "approved_at": "2025-01-15T10:30:00Z"}
       cmd.upsert_metadata(key="my_plugin:review", value=json.dumps(data))
    ```
##  Notes 
  - Metadata entries are command-specific — the same key can have different values for different commands
  - There is no built-in versioning; updating a key overwrites the previous value
  - The system does not enforce any schema on metadata values — validation is the responsibility of the implementing code
  - The `key` field supports up to 256 characters
  - To collect metadata from a user in the chart rather than writing it from a plugin, see the [Command Metadata Create form effect](/sdk/command-metadata-create-form-effect/)
----- END PAGE https://docs.canvasmedical.com/sdk/effect-command-metadata/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-command-validation/
The `CommandValidationErrorEffect` returns structured error messages that are displayed to users in the Canvas UI. It serves two purposes:
  - **Validate a command** as it is entered in the Canvas UI, surfacing problems before it can be committed (`__POST_VALIDATION` events).
  - **Block a deletion** by returning the effect from a command's `__PRE_DELETE` handler.
In both cases you build a `CommandValidationErrorEffect`, attach one or more error messages, and return it from your handler.
Where these errors are enforced differs by event: `__POST_VALIDATION` errors block a commit **only in the Canvas UI** , while `__PRE_DELETE` errors block a deletion through **both** the Canvas UI and the SDK [commands module](/sdk/commands/). Each section below covers the specifics.
##  The effect 
###  CommandValidationErrorEffect 
The `CommandValidationErrorEffect` class accepts an optional list of `ValidationError` objects during initialization:
Attribute | Type | Required | Description  
---|---|---|---  
`errors` | list[ValidationError] | optional | List of validation errors to be displayed to the user.  
###  ValidationError 
Each `ValidationError` object represents a single validation error message:
Attribute | Type | Required | Description  
---|---|---|---  
`message` | String | required | The validation error message to display. Must not be empty.  
###  Building the errors 
Add errors incrementally with `add_error()`, which returns `self` so calls can be chained:
    ```python
    effect = CommandValidationErrorEffect()
    effect.add_error("Narrative is required").add_error("Please provide details about the plan")
    return [effect.apply()]
    ```
Or pass a list of `ValidationError` objects to the constructor:
    ```python
    from canvas_sdk.commands.validation import CommandValidationErrorEffect, ValidationError
    errors = [
        ValidationError("Narrative is required"),
        ValidationError("Narrative must be at least 10 characters long"),
    ]
    effect = CommandValidationErrorEffect(errors=errors)
    return [effect.apply()]
    ```
##  Validate a command 
Use `CommandValidationErrorEffect` with a command's `__POST_VALIDATION` event to check the command as it is entered and surface problems before it is committed. These events follow the pattern:
`{COMMAND_KEY}_COMMAND__POST_VALIDATION`
The following command types fire `__POST_VALIDATION` and can be validated with this effect:
  - `ADJUST_PRESCRIPTION_COMMAND__POST_VALIDATION`
  - `ALLERGY_COMMAND__POST_VALIDATION`
  - `APPROVE_REFILL_COMMAND__POST_VALIDATION`
  - `ASSESS_CODING_GAP_COMMAND__POST_VALIDATION`
  - `ASSESS_COMMAND__POST_VALIDATION`
  - `CANCEL_PRESCRIPTION_COMMAND__POST_VALIDATION`
  - `CHANGE_MEDICATION_COMMAND__POST_VALIDATION`
  - `CHART_SECTION_REVIEW_COMMAND__POST_VALIDATION`
  - `CLIPBOARD_COMMAND__POST_VALIDATION`
  - `CLOSE_GOAL_COMMAND__POST_VALIDATION`
  - `CREATE_CODING_GAP_COMMAND__POST_VALIDATION`
  - `DEFER_CODING_GAP_COMMAND__POST_VALIDATION`
  - `DENY_REFILL_COMMAND__POST_VALIDATION`
  - `DIAGNOSE_COMMAND__POST_VALIDATION`
  - `EDUCATIONAL_MATERIAL_COMMAND__POST_VALIDATION`
  - `FAMILY_HISTORY_COMMAND__POST_VALIDATION`
  - `FOLLOW_UP_COMMAND__POST_VALIDATION`
  - `GOAL_COMMAND__POST_VALIDATION`
  - `HISTORY_OF_PRESENT_ILLNESS_COMMAND__POST_VALIDATION`
  - `IMAGING_ORDER_COMMAND__POST_VALIDATION`
  - `IMMUNIZATION_STATEMENT_COMMAND__POST_VALIDATION`
  - `IMMUNIZE_COMMAND__POST_VALIDATION`
  - `INSTRUCT_COMMAND__POST_VALIDATION`
  - `LAB_ORDER_COMMAND__POST_VALIDATION`
  - `MEDICAL_HISTORY_COMMAND__POST_VALIDATION`
  - `MEDICATION_STATEMENT_COMMAND__POST_VALIDATION`
  - `PERFORM_COMMAND__POST_VALIDATION`
  - `PHYSICAL_EXAM_COMMAND__POST_VALIDATION`
  - `PLAN_COMMAND__POST_VALIDATION`
  - `POC_LAB_TEST_COMMAND__POST_VALIDATION`
  - `PRESCRIBE_COMMAND__POST_VALIDATION`
  - `QUESTIONNAIRE_COMMAND__POST_VALIDATION`
  - `REASON_FOR_VISIT_COMMAND__POST_VALIDATION`
  - `REFERENCE_COMMAND__POST_VALIDATION`
  - `REFER_COMMAND__POST_VALIDATION`
  - `REFILL_COMMAND__POST_VALIDATION`
  - `REMOVE_ALLERGY_COMMAND__POST_VALIDATION`
  - `RESOLVE_CONDITION_COMMAND__POST_VALIDATION`
  - `ROS_COMMAND__POST_VALIDATION`
  - `SNOOZE_PROTOCOL_COMMAND__POST_VALIDATION`
  - `STOP_MEDICATION_COMMAND__POST_VALIDATION`
  - `STRUCTURED_ASSESSMENT_COMMAND__POST_VALIDATION`
  - `SURGICAL_HISTORY_COMMAND__POST_VALIDATION`
  - `TASK_COMMAND__POST_VALIDATION`
  - `UPDATE_DIAGNOSIS_COMMAND__POST_VALIDATION`
  - `UPDATE_GOAL_COMMAND__POST_VALIDATION`
  - `VALIDATE_CODING_GAP_COMMAND__POST_VALIDATION`
  - `VISUAL_EXAM_FINDING_COMMAND__POST_VALIDATION`
  - `VITALS_COMMAND__POST_VALIDATION`
The Custom Command, Imaging Review, Lab Review, Referral Review, Uncategorized Document Review commands do **not** fire `__POST_VALIDATION`, so they can't be validated with this effect.
The following handler validates a Plan command to ensure it meets specific requirements:
    ```python
    from canvas_sdk.commands import PlanCommand
    from canvas_sdk.commands.validation import CommandValidationErrorEffect
    from canvas_sdk.effects import Effect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from logger import log
    class MyHandler(BaseHandler):
        """
        Example protocol demonstrating command validation.
        This protocol validates Plan commands to ensure they meet
        organizational requirements before being committed.
        """
        RESPONDS_TO = EventType.Name(EventType.PLAN_COMMAND__POST_VALIDATION)
        def compute(self) -> list[Effect]:
            log.info("Running command validation protocol.")
            # Extract command fields from context
            narrative = self.context["fields"]["narrative"]
            # Create the validation effect
            effect = CommandValidationErrorEffect()
            # Perform validation checks
            if not narrative or not narrative.strip():
                effect.add_error("Narrative is required and cannot be empty")
            elif len(narrative.strip()) < 10:
                effect.add_error("Narrative must be at least 10 characters long")
            # Check for prohibited content
            prohibited_terms = ["TODO", "TBD", "FIXME"]
            if any(term in narrative.upper() for term in prohibited_terms):
                effect.add_error("Narrative cannot contain placeholder text (TODO, TBD, FIXME)")
            # Check for required keywords (example: follow-up plans must mention timeline)
            if "follow" in narrative.lower() and not any(word in narrative.lower() for word in ["week", "month", "day"]):
                effect.add_error("Follow-up plans must include a specific timeline")
            # Return the effect
            return [effect.apply()]
    ```
When validation errors are returned, the Canvas UI shows them to the user — the command's action buttons are disabled and the messages appear as a tooltip — so the command can't be committed there. Multiple errors can be returned at once, and all are displayed.
> **Note:** `__POST_VALIDATION` only gates committing **in the Canvas UI**. A `.commit()` made through the SDK [commands module](/sdk/commands/) is **not** blocked by these errors — the command still commits. Use it as a UI guardrail, not as an enforced rule on SDK-driven commits. (Blocking a deletion, below, _does_ work through both the UI and the SDK.)
##  Block a deletion 
Return a `CommandValidationErrorEffect` from a command's `__PRE_DELETE` handler to block its deletion. Unlike `__POST_VALIDATION`, this works through **both** the Canvas UI and the SDK [commands module](/sdk/commands/): the deletion is aborted, the surrounding transaction is rolled back, and the error messages are returned to whatever initiated the delete — a [`delete()`](/sdk/commands/) call or a delete in the UI. For SDK-initiated deletes, the error is written to `canvas logs`. Pre-delete events follow the pattern:
`{COMMAND_KEY}_COMMAND__PRE_DELETE`
`__PRE_DELETE` is fired by the following command types (every command except Chart Section Review):
  - `ADJUST_PRESCRIPTION_COMMAND__PRE_DELETE`
  - `ALLERGY_COMMAND__PRE_DELETE`
  - `APPROVE_REFILL_COMMAND__PRE_DELETE`
  - `ASSESS_CODING_GAP_COMMAND__PRE_DELETE`
  - `ASSESS_COMMAND__PRE_DELETE`
  - `CANCEL_PRESCRIPTION_COMMAND__PRE_DELETE`
  - `CHANGE_MEDICATION_COMMAND__PRE_DELETE`
  - `CLIPBOARD_COMMAND__PRE_DELETE`
  - `CLOSE_GOAL_COMMAND__PRE_DELETE`
  - `CREATE_CODING_GAP_COMMAND__PRE_DELETE`
  - `CUSTOM_COMMAND_COMMAND__PRE_DELETE`
  - `DEFER_CODING_GAP_COMMAND__PRE_DELETE`
  - `DENY_REFILL_COMMAND__PRE_DELETE`
  - `DIAGNOSE_COMMAND__PRE_DELETE`
  - `EDUCATIONAL_MATERIAL_COMMAND__PRE_DELETE`
  - `FAMILY_HISTORY_COMMAND__PRE_DELETE`
  - `FOLLOW_UP_COMMAND__PRE_DELETE`
  - `GOAL_COMMAND__PRE_DELETE`
  - `HISTORY_OF_PRESENT_ILLNESS_COMMAND__PRE_DELETE`
  - `IMAGING_ORDER_COMMAND__PRE_DELETE`
  - `IMAGING_REVIEW_COMMAND__PRE_DELETE`
  - `IMMUNIZATION_STATEMENT_COMMAND__PRE_DELETE`
  - `IMMUNIZE_COMMAND__PRE_DELETE`
  - `INSTRUCT_COMMAND__PRE_DELETE`
  - `LAB_ORDER_COMMAND__PRE_DELETE`
  - `LAB_REVIEW_COMMAND__PRE_DELETE`
  - `MEDICAL_HISTORY_COMMAND__PRE_DELETE`
  - `MEDICATION_STATEMENT_COMMAND__PRE_DELETE`
  - `PERFORM_COMMAND__PRE_DELETE`
  - `PHYSICAL_EXAM_COMMAND__PRE_DELETE`
  - `PLAN_COMMAND__PRE_DELETE`
  - `POC_LAB_TEST_COMMAND__PRE_DELETE`
  - `PRESCRIBE_COMMAND__PRE_DELETE`
  - `QUESTIONNAIRE_COMMAND__PRE_DELETE`
  - `REASON_FOR_VISIT_COMMAND__PRE_DELETE`
  - `REFERENCE_COMMAND__PRE_DELETE`
  - `REFERRAL_REVIEW_COMMAND__PRE_DELETE`
  - `REFER_COMMAND__PRE_DELETE`
  - `REFILL_COMMAND__PRE_DELETE`
  - `REMOVE_ALLERGY_COMMAND__PRE_DELETE`
  - `RESOLVE_CONDITION_COMMAND__PRE_DELETE`
  - `ROS_COMMAND__PRE_DELETE`
  - `SNOOZE_PROTOCOL_COMMAND__PRE_DELETE`
  - `STOP_MEDICATION_COMMAND__PRE_DELETE`
  - `STRUCTURED_ASSESSMENT_COMMAND__PRE_DELETE`
  - `SURGICAL_HISTORY_COMMAND__PRE_DELETE`
  - `TASK_COMMAND__PRE_DELETE`
  - `UNCATEGORIZED_DOCUMENT_REVIEW_COMMAND__PRE_DELETE`
  - `UPDATE_DIAGNOSIS_COMMAND__PRE_DELETE`
  - `UPDATE_GOAL_COMMAND__PRE_DELETE`
  - `VALIDATE_CODING_GAP_COMMAND__PRE_DELETE`
  - `VISUAL_EXAM_FINDING_COMMAND__PRE_DELETE`
  - `VITALS_COMMAND__PRE_DELETE`
The following handler prevents deletion of a Refer command once its priority has been set to `Urgent` or `STAT`, so high-priority referrals can't be removed by mistake. The command's field values are available on the event context, so no extra lookup is needed:
    ```python
    from canvas_sdk.commands import ReferCommand
    from canvas_sdk.commands.validation import CommandValidationErrorEffect
    from canvas_sdk.effects import Effect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class BlockUrgentReferralDeletionHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.REFER_COMMAND__PRE_DELETE)
        def compute(self) -> list[Effect]:
            priority = self.context["fields"].get("priority")
            protected = {ReferCommand.Priority.URGENT.value, ReferCommand.Priority.STAT.value}
            if priority in protected:
                effect = CommandValidationErrorEffect()
                effect.add_error(
                    f"A {priority}-priority referral can't be deleted. "
                    "Lower its priority first if you need to remove it."
                )
                return [effect.apply()]
            return []
    ```
When a delete is attempted on an `Urgent` or `STAT` referral, it is blocked and the error message is returned to whoever initiated it.
For more information about command events and their context objects, see the [Events documentation](/sdk/events/).
----- END PAGE https://docs.canvasmedical.com/sdk/effect-command-validation/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-compound-medication/
The Compound Medication effects enable the creation and management of compound medication formulations within the Canvas system. These effects support the customization of medications prepared by compounding pharmacies according to prescriptions.
##  Create Compound Medication 
The `CreateCompoundMedication` effect creates a new compound medication formulation in the system.
###  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`formulation` | `str` | The compound medication formulation (max 105 characters) | Yes  
`potency_unit_code` | `str` | The unit of measurement for the medication | Yes  
`controlled_substance` | `str` | The controlled substance schedule | Yes  
`controlled_substance_ndc` | `str` or `None` | NDC code for controlled substances (dashes removed) | No*  
`active` | `bool` | Whether the compound medication is active | No  
*Required when `controlled_substance` is not "N" (None)
###  Example Usage 
    ```python
    from canvas_sdk.effects.compound_medications import CompoundMedication as CompoundMedicationEffect
    from canvas_sdk.handlers.base import BaseHandler
    from canvas_sdk.events import EventType
    from canvas_sdk.v1.data.compound_medication import CompoundMedication as CompoundMedicationModel
    class CompoundMedicationCreator(BaseHandler):
      RESPONDS_TO = [EventType.Name(EventType.PATIENT_CREATED)]
      def compute(self):
        # Create a non-controlled compound medication
        compound_med = CompoundMedicationEffect(
          formulation="Testosterone 200mg/mL in Grapeseed Oil",
          potency_unit_code=CompoundMedicationModel.PotencyUnit.Milliliter,
          controlled_substance=CompoundMedicationModel.ControlledSubstanceSchedule.SCHEDULE_NOT_SCHEDULED,
          active=True
        )
        # Create a controlled substance compound medication
        controlled_compound = CompoundMedicationEffect(
          formulation="Hydrocodone 5mg/Acetaminophen 325mg Capsule",
          potency_unit_code=CompoundMedicationModel.PotencyUnit.Capsule,
          controlled_substance=CompoundMedicationModel.ControlledSubstanceSchedule.SCHEDULE_II,
          controlled_substance_ndc="12345678901",
          active=True
        )
        return [compound_med.create(), controlled_compound.create()]
    ```
##  Update Compound Medication 
The `UpdateCompoundMedication` effect modifies an existing compound medication formulation.
###  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`compound_medication_id` | `str` | The ID of the compound medication to update | Yes  
`formulation` | `str` or `None` | The compound medication formulation (max 105 characters) | No  
`potency_unit_code` | `str` or `None` | The unit of measurement for the medication | No  
`controlled_substance` | `str` or `None` | The controlled substance schedule | No  
`controlled_substance_ndc` | `str` or `None` | NDC code for controlled substances (dashes removed) | No  
`active` | `bool` or `None` | Whether the compound medication is active | No  
###  Example Usage 
    ```python
    from canvas_sdk.effects.compound_medications import CompoundMedication as CompoundMedicationEffect
    from canvas_sdk.handlers.base import BaseHandler
    from canvas_sdk.v1.data.compound_medication import CompoundMedication as CompoundMedicationModel
    from canvas_sdk.events import EventType
    class CompoundMedicationUpdater(BaseHandler):
      RESPONDS_TO = [EventType.Name(EventType.PLUGIN_CREATED)]
      def compute(self):
        # Find a compound medication to update
        compound_med = CompoundMedicationModel.objects.filter(
            formulation__contains="Testosterone"
        ).first()
        if compound_med:
            # Update to make it a controlled substance
            update_effect = CompoundMedicationEffect(
                compound_medication_id=str(compound_med.id),
                controlled_substance="III",
                controlled_substance_ndc="98765432101"
            )
            return [update_effect.update()]
        return []
    ```
##  Implementation Details 
  - **Formulation Validation** : The formulation field is limited to 105 characters
  - **NDC Formatting** : Any dashes in the NDC code are automatically removed during processing
  - **Cross-field Validation** : When a controlled substance schedule is specified (anything other than "N"), an NDC code must be provided
  - **Default Values** : If not specified, `active` defaults to `True` for new compound medications
  - **Potency Unit Codes** : Must use valid codes as defined in the [PotencyUnit](/sdk/data-compound-medication/#potencyunit) enumeration
  - **Controlled Substance Schedules** : Must use valid values as defined in the [ControlledSubstanceSchedule](/sdk/data-compound-medication/#controlledsubstanceschedule) enumeration
##  Validation 
Both effects perform validation before execution:
###  Create Effect Validation: 
  - Validates all required fields are provided
  - Ensures `potency_unit_code` is a valid value from the PotencyUnit enumeration
  - Ensures `controlled_substance` is a valid schedule from the ControlledSubstanceSchedule enumeration
  - Validates NDC is provided for controlled substances (when schedule is not "N")
  - Checks formulation length does not exceed 105 characters
###  Update Effect Validation: 
  - Verifies the compound medication exists before updating
  - Validates any provided fields follow the same rules as creation
  - Ensures NDC is provided if updating to a controlled substance
  - Only updates fields that are explicitly provided (partial updates supported)
##  Error Handling 
If validation fails, a `ValidationError` is raised with detailed error messages indicating which fields failed validation and why. Error messages are aggregated to provide comprehensive feedback about all validation failures at once.
----- END PAGE https://docs.canvasmedical.com/sdk/effect-compound-medication/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-configure-command-buttons/
The `ConfigureCommandButtons` effect allows plugins to hide or disable the command buttons that appear in specific areas of the patient chart — such as the conditions section, medications section, or protocol cards.
##  Locations 
The `Location` enum defines which areas of the chart can be configured:
Value | Area  
---|---  
`CONDITIONS` | Conditions chart summary section  
`MEDICATIONS` | Medications chart summary section  
`ALLERGIES` | Allergies chart summary section  
`GOALS` | Goals chart summary section  
`VITALS` | Vitals chart summary section  
`IMMUNIZATIONS` | Immunizations chart summary section  
`SURGICAL_HISTORY` | Surgical history chart summary section  
`FAMILY_HISTORY` | Family history chart summary section  
`SOCIAL_DETERMINANTS` | Social determinants chart summary section  
`CARE_TEAMS` | Care teams chart summary section  
`CODING_GAPS` | Coding gaps chart summary section  
`QUALITY_PROTOCOLS` | Quality protocol result cards  
`LAB_REVIEWS` | Lab report review result cards  
`IMAGING_REVIEWS` | Imaging report review result cards  
`REFERRAL_REVIEWS` | Referral report review result cards  
`DOCUMENT_REVIEWS` | Uncategorized document review result cards  
##  Visibility 
Each location can be configured with one of three visibility values:
Value | Behaviour  
---|---  
`VISIBLE` | Buttons are shown and interactive (default when not listed)  
`HIDDEN` | Buttons are not rendered  
`DISABLED` | Buttons are rendered but not interactive  
##  Attributes 
Each entry in `locations` is a `LocationConfig` with the following attributes:
Attribute | Required | Type | Description  
---|---|---|---  
`location` | yes | `Location` | The chart area to configure  
`visibility` | yes | `Visibility` | The visibility state for that area  
Top-level | Required | Type | Description  
---|---|---|---  
`patient_id` | yes | `str` | The patient id  
`locations` | no | `list[LocationConfig]` | Areas to configure. Areas not listed retain their default visible state.  
##  Validation 
Duplicate `location` values in the `locations` list will raise a `ValidationError` when `apply()` is called.
##  Example: Patient Chart Load 
`PATIENT_TIMELINE__GET_CONFIGURATION` fires every time a patient chart is opened, making it a convenient hook for configuring button visibility on chart load. The example below hides all command buttons across every location:
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.configure_command_buttons import ConfigureCommandButtons
    from canvas_sdk.events import EventType
    from canvas_sdk.protocols import BaseProtocol
    Location = ConfigureCommandButtons.Location
    LocationConfig = ConfigureCommandButtons.LocationConfig
    Visibility = ConfigureCommandButtons.Visibility
    class HideButtonsOnChartLoad(BaseProtocol):
        RESPONDS_TO = EventType.Name(EventType.PATIENT_TIMELINE__GET_CONFIGURATION)
        def compute(self) -> list[Effect]:
            return [
                ConfigureCommandButtons(
                    patient_id=self.target,
                    locations=[
                        LocationConfig(location=loc, visibility=Visibility.HIDDEN)
                        for loc in Location
                    ],
                ).apply()
            ]
    ```
##  Example: Note Applications 
One use case is toggling chart buttons alongside a `NoteApplication`. When the application tab opens, `on_open` disables chart buttons. When the provider switches back to the note body, Canvas sends a `NOTE_TAB_CHANGE` message to the iframe, which calls a `SimpleAPI` endpoint to restore them.
###  Python 
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.configure_command_buttons import ConfigureCommandButtons
    from canvas_sdk.effects.launch_modal import LaunchModalEffect
    from canvas_sdk.effects.simple_api import JSONResponse, Response
    from canvas_sdk.handlers.application import NoteApplication
    from canvas_sdk.handlers.simple_api import SimpleAPI, StaffSessionAuthMixin, api
    from canvas_sdk.templates import render_to_string
    Location = ConfigureCommandButtons.Location
    LocationConfig = ConfigureCommandButtons.LocationConfig
    Visibility = ConfigureCommandButtons.Visibility
    class MyChartingApp(NoteApplication):
        NAME = "My Charting App"
        IDENTIFIER = "my-plugin:charting-app"
        def on_open(self) -> list[Effect]:
            patient_id = self.event.context.get("patient", {}).get("id")
            return [
                LaunchModalEffect(
                    target=LaunchModalEffect.TargetType.NOTE,
                    content=render_to_string(
                        "templates/charting_app.html",
                        context={"identifier": self.IDENTIFIER},
                    ),
                    title="My Charting App",
                ).apply(),
                ConfigureCommandButtons(
                    patient_id=patient_id,
                    locations=[
                        LocationConfig(location=loc, visibility=Visibility.DISABLED)
                        for loc in Location
                    ],
                ).apply(),
            ]
    class CommandButtonsApi(StaffSessionAuthMixin, SimpleAPI):
        @api.post("/configure-buttons/disable")
        def disable(self) -> list[Response | Effect]:
            patient_id = self.request.json().get("patient_id")
            return [
                JSONResponse({"ok": True}),
                ConfigureCommandButtons(
                    patient_id=patient_id,
                    locations=[
                        LocationConfig(location=loc, visibility=Visibility.DISABLED)
                        for loc in Location
                    ],
                ).apply(),
            ]
        @api.post("/configure-buttons/enable")
        def enable(self) -> list[Response | Effect]:
            patient_id = self.request.json().get("patient_id")
            return [
                JSONResponse({"ok": True}),
                ConfigureCommandButtons(
                    patient_id=patient_id,
                    locations=[
                        LocationConfig(location=loc, visibility=Visibility.VISIBLE)
                        for loc in Location
                    ],
                ).apply(),
            ]
    ```
###  Template 
The iframe listens for `NOTE_TAB_CHANGE` messages from Canvas and calls the appropriate endpoint. When `tab` is `"note"` the provider has switched back to the note body; when `tab` matches the application's identifier the application tab is active.
    ```html
    <!-- templates/charting_app.html -->
    <script>
      var port;
      var myIdentifier = '';
      function post(endpoint, patientId) {
        fetch('/plugin-io/api/my_plugin/configure-buttons/' + endpoint, {
          method: 'POST',
          body: JSON.stringify({ patient_id: patientId })
        });
      }
      window.addEventListener('message', function(event) {
        if (event.data?.type === 'INIT_CHANNEL') {
          port = event.ports[0];
          port.onmessage = function(e) {
            if (e.data?.type === 'NOTE_TAB_CHANGE') {
              if (e.data.tab === 'note') post('enable', e.data.patient.id);
              else if (e.data.tab === myIdentifier) post('disable', e.data.patient.id);
            }
          };
        }
      });
    </script>
    ```
Both `MyChartingApp` and `CommandButtonsApi` should be registered as `handlers` in your `CANVAS_MANIFEST.json`.
----- END PAGE https://docs.canvasmedical.com/sdk/effect-configure-command-buttons/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-create-ccda-export/
The `CreateCCDA` effect creates a C-CDA document for a patient with the provided XML content. This effect allows plugins to store C-CDA XML documents, which can be used for clinical document exchange, patient summaries, or referrals.
This effect stores a C-CDA document, generating or otherwise sourcing that document is the responsibility of the plugin.
##  Attributes 
Name | Type | Required | Description  
---|---|---|---  
`patient_id` | `str` | Yes | The patient's key (UUID).  
`content` | `str` | Yes | The C-CDA XML content as a string. Must be valid XML.  
`document_type` | `DocumentType` | No | Type of C-CDA document. Defaults to `DocumentType.CCD`.  
##  DocumentType Enum 
Value | Description  
---|---  
`CCD` | Continuity of Care Document (default)  
`REFERRAL` | Referral document  
##  Validation 
The effect performs the following validations before execution:
  - **Patient Exists** : Verifies that a patient with the given `patient_id` exists in the system.
  - **Valid XML** : Validates that the `content` field contains well-formed XML. Malformed XML will result in a validation error.
  - **Required Fields** : Both `patient_id` and `content` must be non-empty strings.
##  Example Usage 
###  Basic CCD Creation 
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.ccda import CreateCCDA, DocumentType
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class MyHandler(BaseHandler):
        RESPONDS_TO = [EventType.Name(EventType.PATIENT_UPDATED)]
        def compute(self) -> list[Effect]:
            # Sample C-CDA XML content
            ccda_xml = """<?xml version="1.0" encoding="UTF-8"?>
            <ClinicalDocument xmlns="urn:hl7-org:v3">
                <typeId root="2.16.840.1.113883.1.3" extension="POCD_HD000040"/>
                <templateId root="2.16.840.1.113883.10.20.22.1.1"/>
                <id root="document-id"/>
                <code code="34133-9" displayName="Summarization of Episode Note"
                      codeSystem="2.16.840.1.113883.6.1"/>
                <title>Patient Summary</title>
                <effectiveTime value="20240101120000"/>
                <confidentialityCode code="N" codeSystem="2.16.840.1.113883.5.25"/>
                <languageCode code="en-US"/>
            </ClinicalDocument>"""
            effect = CreateCCDA(
                patient_id="a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                content=ccda_xml,
                document_type=DocumentType.CCD,
            )
            return [effect.apply()]
    ```
###  Creating a Referral Document 
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.ccda import CreateCCDA, DocumentType
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class MyHandler(BaseHandler):
        RESPONDS_TO = [EventType.Name(EventType.PATIENT_UPDATED)]
        def compute(self) -> list[Effect]:
            referral_xml_content = "<ClinicalDocument>...</ClinicalDocument>"
            effect = CreateCCDA(
                patient_id="a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                content=referral_xml_content,
                document_type=DocumentType.REFERRAL,
            )
            return [effect.apply()]
    ```
###  Using Default Document Type 
When `document_type` is not specified, it defaults to `CCD`:
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.ccda import CreateCCDA
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class MyHandler(BaseHandler):
        RESPONDS_TO = [EventType.Name(EventType.PATIENT_UPDATED)]
        def compute(self) -> list[Effect]:
            ccda_xml = "<ClinicalDocument>...</ClinicalDocument>"
            effect = CreateCCDA(
                patient_id="a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                content=ccda_xml,
            )
            # document_type defaults to DocumentType.CCD
            return [effect.apply()]
    ```
##  Use Cases 
  - **Clinical Document Exchange** : Generate C-CDAs for sharing patient information with external systems or providers.
  - **Patient Summaries** : Create Continuity of Care Documents containing a patient's clinical summary.
  - **Referral Documentation** : Generate referral documents when referring patients to specialists.
  - **Integration with External Systems** : Produce standardized C-CDA documents for healthcare interoperability.
##  Notes 
  - The C-CDA XML content is stored as a file on the patient's record. You can access the record in Settings > CCDAs to be able to view, transmit, or download the file.
----- END PAGE https://docs.canvasmedical.com/sdk/effect-create-ccda-export/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-create-patient-external-identifier/
Creates a new external identifier for a patient.
###  Parameters 
Name | Type | Description  
---|---|---  
patient_id | UUID | The unique identifier of the patient.  
system | String | The system for the external identifier (url).  
value | String | The value of the external identifier.  
###  Example 
    ```python
    from canvas_sdk.effects.patient import CreatePatientExternalIdentifier
    effect = CreatePatientExternalIdentifier(
        patient_id="1eed3ea2a8d546a1b681a2a45de1d790",
        system="https://www.va.gov/",
        value="VET123456"
    )
    effect.create()
    ```
This effect will create a new external identifier for the specified patient.
----- END PAGE https://docs.canvasmedical.com/sdk/effect-create-patient-external-identifier/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-create-patient-preferred-pharmacies/
Creates preferred pharmacies for a patient.
###  Parameters 
Name | Type | Description  
---|---|---  
patient_id | `str` or `UUID` | The unique identifier of the patient.  
pharmacies | `list[PatientPreferredPharmacy]` | List of pharmacies to create.  
###  PatientPreferredPharmacy 
The `PatientPreferredPharmacy` dataclass represents a patient's preferred pharmacy, and if it's their default pharmacy.
###  Validation 
When this effect is interpreted, Canvas validates the `ncpdp_id` before setting the preferred pharmacy. If the `ncpdp_id` is invalid or does not exist, the effect will fail.
To ensure the `ncpdp_id` exists before using this effect, you can verify it using Canvas's [pharmacy HTTP utility](/sdk/utils/#making-requests-to-the-pharmacy-service) to check the pharmacy beforehand.
####  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`ncpdp_id` | `str` | The NCPDC identifier of the pharmacy. | Yes  
`default` | `bool` | Indicates if this is the patient's default pharmacy. | No, defaults to `False`  
###  Example 
    ```python
    from canvas_sdk.effects.patient import CreatePatientPreferredPharmacies, PatientPreferredPharmacy
    from canvas_sdk.v1.data import Patient as PatientModel
    first_patient_id = PatientModel.objects.values_list("id", flat=True).first()
    preferred_pharmacies_effect = CreatePatientPreferredPharmacies(
                                    pharmacies=[PatientPreferredPharmacy(ncpdp_id="0586163", default=True)],
                                    patient_id=first_patient_id
    )
    preferred_pharmacies_effect.create()
    ```
This effect will create a new preferred pharmacy for the specified patient.
Since the `default` attribute is set to `True`, it will mark this pharmacy as the patient's default preferred pharmacy.
----- END PAGE https://docs.canvasmedical.com/sdk/effect-create-patient-preferred-pharmacies/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-data-integration/
Plugins can automate triage of inbound clinical documents in the [Data Integration queue](/sdk/data-integration-task/) — lab reports, imaging reports, faxes, clinical and administrative documents, and other uploaded files awaiting staff review before they're attached to a patient's chart.
Most of these effects work by writing a **prefill suggestion** to the IntegrationTask. The Data Integration UI surfaces that suggestion as a pre-populated value in the staff member's review form — the suggested patient, document type, template values, or reviewer assignment — along with optional annotation badges. A staff member still reviews and commits the change; the plugin doesn't directly mutate the IntegrationTask.
The exceptions are `JunkDocument` and `RemoveDocumentFromPatient`, which act on the IntegrationTask's status or patient link immediately without writing a prefill.
##  Assigning a Reviewer 
To assign a staff member or team as the reviewer for a document in the Data Integration queue, import the `AssignDocumentReviewer` class from `canvas_sdk.effects.data_integration` and create an instance of it.
Attribute |  | Type | Description  
---|---|---|---  
`document_id` | required | string | The `id` of the [IntegrationTask](/sdk/data-integration-task/#integrationtask) document.  
`reviewer_id` | optional | string | The `id` of the [Staff](/sdk/data-staff/#staff) member to assign as reviewer.  
`team_id` | optional | string | The `id` of the [Team](/sdk/data-team/#team) to assign as reviewer.  
`review_mode` | optional | ReviewMode | Review mode. Defaults to `ReviewMode.REVIEW_REQUIRED`.  
`annotations` | optional | list | List of annotations for display in the UI. See Annotations.  
Supply either `reviewer_id` or `team_id`, not both. If both are supplied, the staff reviewer is used and the team is ignored when the Data Integration UI pre-populates the reviewer field. Supplying neither makes the effect a no-op.
###  ReviewMode 
Value | Description  
---|---  
`ReviewMode.REVIEW_REQUIRED` (default) | Document requires explicit review.  
`ReviewMode.ALREADY_REVIEWED` | Document is marked as already reviewed.  
`ReviewMode.REVIEW_NOT_REQUIRED` | Document does not require review.  
An example of assigning a staff reviewer. Annotations render as colored badges next to the reviewer field in the Data Integration UI — useful for surfacing why the plugin chose this reviewer:
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.data_integration import AssignDocumentReviewer, ReviewMode
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class AssignReviewerHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.DOCUMENT_RECEIVED)
        def compute(self) -> list[Effect]:
            return [
                AssignDocumentReviewer(
                    document_id=self.event.target.id,
                    reviewer_id="4150cd20de8a470aa570a852859ac87e",
                    review_mode=ReviewMode.ALREADY_REVIEWED,
                    annotations=[
                        {"text": "Team lead", "color": "#4CAF50"},
                        {"text": "Auto-assigned", "color": "#FF9800"},
                    ],
                ).apply()
            ]
    ```
An example of assigning a team instead of an individual reviewer:
    ```python
    from canvas_sdk.effects.data_integration import AssignDocumentReviewer
    assign_reviewer = AssignDocumentReviewer(
        document_id="d2194110-5c9a-4842-8733-ef09ea5ead11",
        team_id="3f8a2e1c-9b4d-4f5a-8c7e-1d2b3a4c5d6e",
        annotations=[
            {"text": "Routed to Coding team", "color": "#2196F3"},
        ],
    )
    ```
##  Categorizing a Document 
To categorize a document in the Data Integration queue into a specific document type, import the `CategorizeDocument` class from `canvas_sdk.effects.data_integration` and create an instance of it.
Attribute |  | Type | Description  
---|---|---|---  
`document_id` | required | string | The `id` of the [IntegrationTask](/sdk/data-integration-task/#integrationtask) document to categorize.  
`document_type` | required | DocumentType | Document type information for categorizing the document.  
`annotations` | optional | list | List of annotations for display in the UI. See Annotations.  
###  DocumentType 
The `document_type` parameter is a dictionary with the following fields:
Key |  | Type | Description  
---|---|---|---  
`key` | required | string | The unique key identifying the document type. Must match a key from the Supported Document Types table below.  
`name` | required | string | The human-readable name of the document type. Should match the catalog's `Name` for that key.  
`report_type` | required | string | The type of report. Must be `"CLINICAL"` or `"ADMINISTRATIVE"`.  
`template_type` | required | string | null | Must be `"LabReportTemplate"`, `"ImagingReportTemplate"`, `"SpecialtyReportTemplate"`, or `null`.  
###  Supported Document Types 
Canvas's built-in document type catalog. Use the `Key` value in your `document_type` dict; the other columns show the catalog's `report_type` and the matching parse template (when applicable).
Name | Key | Report Type | Template Type  
---|---|---|---  
Advance Beneficiary Notice | `5375e6ae238e41b4972717174be99d10` | ADMINISTRATIVE | `null`  
Advance Directive / Living Will | `1d5a4821140dab935e90d9d73bfd7a35` | ADMINISTRATIVE | `null`  
CDL (Commercial Driver License) | `7639c58fb4b75e2ff74270787eda80a7` | ADMINISTRATIVE | `null`  
Care Management | `6b4b539a233145fe871e8ac703f39fcb` | CLINICAL | `null`  
Disability Form | `a5fd8d81026747c0b01f757b7935f82a` | ADMINISTRATIVE | `null`  
Emergency Department Report | `67037fd377654984b8b368b47d0ab0e4` | CLINICAL | `null`  
External Medical Records | `b61d0a4ebf4316a1a3beea32bec88052` | CLINICAL | `null`  
Handicap Parking Permit | `649a852657357c20491856f4eb7a2690` | ADMINISTRATIVE | `null`  
Home Care Report | `d368eaa8f1b2419cb792bb7876bfac8a` | CLINICAL | `null`  
Hospital Discharge Summary | `6be998e1335a4d9689cae33ec7ed6968` | CLINICAL | `null`  
Hospital History & Physical | `d9060893790744589c5252ddb81b785e` | CLINICAL | `null`  
Imaging Report | `87041869c5954337b84fd10094fe5c0a` | CLINICAL | `ImagingReportTemplate`  
In-Office Testing | `372e7248ba944dbeab54712078c0ec44` | CLINICAL | `null`  
Insurance Card | `2551841bcfd34e1aa839cb1e3b7ef48f` | ADMINISTRATIVE | `null`  
Insurer Prior Authorization | `0394e7a3a5c847f495414e7511d12543` | ADMINISTRATIVE | `null`  
Lab Report | `f605e084dcad4beca16c0f62e6586d76` | CLINICAL | `LabReportTemplate`  
Medicaid Documents | `e02e0f6dc76d42aaa61384c85ca90830` | ADMINISTRATIVE | `null`  
Nursing Home | `fc14824cdc9fdcd1e2ce005aa3019d20` | CLINICAL | `null`  
Operative Report | `7f118206607248b7b13409e69c638eba` | CLINICAL | `null`  
POLST (Provider Order for Life Sustaining Treatment) | `cf72e522dd95fa1da1297cf3bf5e54e8` | ADMINISTRATIVE | `null`  
Patient Administrative Intake Form | `b25601a3e8ac543f5f2d7a85006de223` | ADMINISTRATIVE | `null`  
Patient Agreement | `2e16ccd7ad5a4bcf9d21dd51b4d16cb9` | ADMINISTRATIVE | `null`  
Patient Assistance | `ebd8c4f6f35b4c008e512d0e3c666e95` | ADMINISTRATIVE | `null`  
Patient Clinical Intake Form | `8c9ca86c76704d57a775cd6f48a02b6c` | CLINICAL | `null`  
Patient Consent | `7ce5a6eefedcff89a5a460f6be89d308` | ADMINISTRATIVE | `null`  
Physical Exams | `b1146b76cd2c4b488c964cf497fb1dce` | CLINICAL | `null`  
Power of Attorney | `1ff5f640868528e633a5d45f2142161e` | ADMINISTRATIVE | `null`  
Prescription Card | `0d002c5fe86c44b0a63c08e70e0df37c` | ADMINISTRATIVE | `null`  
Prescription Refill Request | `714a8229339b4af3989403a308bdcbfb` | CLINICAL | `null`  
Rehabilitation Report | `660f6fce32a64b3f9817f4bad3d56c79` | CLINICAL | `null`  
Release of Information Request | `2283372cb0fa4962a6fcff2eb3ca080b` | ADMINISTRATIVE | `null`  
Specialist Consult Report | `f0f1398f6f4640d29e4ff80d5481eb3f` | CLINICAL | `SpecialtyReportTemplate`  
Uncategorized Administrative Document | `7ebe754f4c3b860cf80d3aa9ebd8494c` | ADMINISTRATIVE | `null`  
Uncategorized Clinical Document | `52ef59487ecabc9cdd645c21c7a35458` | CLINICAL | `null`  
Worker's Compensation Documents | `54a06a3f06b48cbebf8dec88707272c3` | ADMINISTRATIVE | `null`  
###  Example 
The `DOCUMENT_RECEIVED` event context includes `available_document_types`, a list of every document type the instance supports — each entry carries the `key`, `name`, `report_type`, and `template_type` you'll need to construct a `CategorizeDocument` effect. The idiomatic pattern is to read from that list rather than hardcode catalog values, so a plugin keeps working as the catalog changes.
The handler below matches by `name` (using the Supported Document Types table above as a reference for what names to expect) and forwards the matched values to the effect. `ReportType` and `TemplateType` enum instances are constructed explicitly from the context's string values — the SDK rejects raw strings.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.data_integration import CategorizeDocument
    from canvas_sdk.effects.data_integration.types import ReportType, TemplateType
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class CategorizeLabReports(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.DOCUMENT_RECEIVED)
        def compute(self) -> list[Effect]:
            available = self.event.context.get("available_document_types", [])
            lab_report = next((dt for dt in available if dt["name"] == "Lab Report"), None)
            if not lab_report:
                return []
            template_type = lab_report.get("template_type")
            return [
                CategorizeDocument(
                    document_id=self.event.target.id,
                    document_type={
                        "key": lab_report["key"],
                        "name": lab_report["name"],
                        "report_type": ReportType(lab_report["report_type"]),
                        "template_type": TemplateType(template_type) if template_type else None,
                    },
                    annotations=[
                        {"text": "AI Categorized", "color": "#4CAF50"},
                    ],
                ).apply()
            ]
    ```
For full end-to-end usage including discovery, error handling, and the other Data Integration effects in a single handler, see the [`data_integration_example` plugin](https://github.com/canvas-medical/canvas-plugins/tree/main/example-plugins/data_integration_example) in canvas-plugins.
##  Linking a Document to a Patient 
To link a document in the Data Integration queue to a patient, import the `LinkDocumentToPatient` class from `canvas_sdk.effects.data_integration` and create an instance of it. The plugin is responsible for matching the patient and supplying their key — the interpreter does not search for matching patients itself.
Attribute |  | Type | Description  
---|---|---|---  
`document_id` | required | string | The `id` of the [IntegrationTask](/sdk/data-integration-task/#integrationtask) document.  
`patient_key` | required | string | The `id` of the [Patient](/sdk/data-patient/#patient) to link the document to.  
`annotations` | optional | list | List of annotations for display in the UI. See Annotations.  
An example of linking a document to a patient:
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.data_integration import LinkDocumentToPatient
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.v1.data.patient import Patient
    class LinkDocumentHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.DOCUMENT_RECEIVED)
        def compute(self) -> list[Effect]:
            document_id = self.event.target.id
            document_title = self.event.context.get("document", {}).get("title", "")
            # Plugin-specific matching logic — e.g., parse the document title or
            # call an OCR/LLM service to extract patient demographics, then look
            # up the patient via the SDK data module.
            patient = Patient.objects.filter(...).first()
            if not patient:
                return []
            return [
                LinkDocumentToPatient(
                    document_id=document_id,
                    patient_key=patient.id,
                    annotations=[
                        {"text": "AI 92%", "color": "#4CAF50"},
                        {"text": f"Matched from '{document_title}'", "color": "#2196F3"},
                    ],
                ).apply()
            ]
    ```
##  Marking a Document as Junk 
To mark a document in the Data Integration queue as junk (spam), import the `JunkDocument` class and create an instance of it.
Attribute |  | Type | Description  
---|---|---|---  
`document_id` | required | string | The `id` of the [IntegrationTask](/sdk/data-integration-task/#integrationtask) document to mark as junk.  
`JunkDocument` only works on IntegrationTasks in early-stage states: **Unread** , **Read** , **Error** , **Unread error** , or **Junk** (already). IntegrationTasks in **Processed** or **Reviewed** states cannot be junked — attempting to do so raises a validation error. The effect also validates that `document_id` resolves to an existing IntegrationTask and is a well-formed UUID; missing, malformed, or unknown IDs raise validation errors before the IntegrationTask is touched.
To avoid the validation error for tasks that have moved past the early-stage states, you can read the [`status`](/sdk/data-integration-task/#integrationtask) field from the SDK data module and preflight-check before emitting the effect.
An example that preflight-checks the IntegrationTask status before junking:
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.data_integration import JunkDocument
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.v1.data.integration_task import IntegrationTask, IntegrationTaskStatus
    class JunkDocumentHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.DOCUMENT_RECEIVED)
        def compute(self) -> list[Effect]:
            document_id = self.event.target.id
            task = IntegrationTask.objects.get(id=document_id)
            # Skip tasks that have already been processed or reviewed
            if task.status in (IntegrationTaskStatus.PROCESSED, IntegrationTaskStatus.REVIEWED):
                return []
            return [JunkDocument(document_id=document_id).apply()]
    ```
##  Prefilling Document Fields 
`PrefillDocumentFields` pre-populates the parse-template fields on an IntegrationTask. **Only three document types support field prefill** — those whose `template_type` is a known parse-template family:
Document type | `template_type` | SDK data module  
---|---|---  
Lab Report | `LabReportTemplate` | [`LabReportTemplate`](/sdk/data-lab-report-template/)  
Imaging Report | `ImagingReportTemplate` | [`ImagingReportTemplate`](/sdk/data-imaging-report-template/)  
Specialist Consult Report | `SpecialtyReportTemplate` | [`SpecialtyReportTemplate`](/sdk/data-specialty-report-template/)  
Document types with inline fields (Patient Consent, Power of Attorney, the Uncategorized variants, and the other ~30 entries in the Supported Document Types catalog) cannot have their fields prefilled by this effect.
> The wire-level effect type for this class is `UPDATE_DOCUMENT_FIELDS`, which is what appears in event logs and the [effects table](/sdk/effects/#data-integration). The class name is `PrefillDocumentFields`.
Attribute |  | Type | Description  
---|---|---|---  
`document_id` | required | string | The `id` of the [IntegrationTask](/sdk/data-integration-task/#integrationtask) document.  
`templates` | required | list[PrefillTemplate] | One or more templates to prefill. Must contain at least one entry.  
`annotations` | optional | list | List of annotations for display in the UI. See Annotations.  
###  PrefillTemplate 
A `PrefillTemplate` is a dictionary with the following keys:
Key |  | Type | Description  
---|---|---|---  
`template_id` | required | int | The integer `dbid` of a `LabReportTemplate`, `ImagingReportTemplate`, or `SpecialtyReportTemplate` record. (Note: this is the integer primary key, not the `id` UUID.) Look this up via the SDK data module — see the example below.  
`template_name` | required | string | The matching template's `.name`.  
`fields` | required | dict[str, PrefillDocumentFieldData] | Map of field name to field data. Keys must match the `label` of a field on the chosen template — iterate `template.fields.all()` to discover the available labels, units, types, and required-ness.  
###  PrefillDocumentFieldData 
A `PrefillDocumentFieldData` is a dictionary with the following keys:
Key |  | Type | Description  
---|---|---|---  
`value` | required | string | The field value, stringified into the form field at render time. The same rules apply to all three template families (`LabReportTemplateField`, `ImagingReportTemplateField`, `SpecialtyReportTemplateField`). Currently supported are fields whose `type` is `"float"`, `"text"`, `"date"`, `"select"`, or `"radio"`. For `"select"` and `"radio"`, the value must exactly match one of the field's `options[*].key` for the dropdown to pre-select.  
`unit` | optional | string | Unit of measurement. Should match the corresponding template field's `units` (`LabReportTemplateField.units`, `ImagingReportTemplateField.units`, or `SpecialtyReportTemplateField.units`).  
`reference_range` | optional | string | Reference range for the value.  
`abnormal` | optional | bool | Whether the value is abnormal.  
`annotations` | optional | list | Per-field annotations. Same shape as Annotations.  
###  Discovering Available Fields 
Before constructing a `PrefillDocumentFields` payload, you can inspect the template to see what labels, units, types, and option keys it defines. The same approach works for `ImagingReportTemplate` and `SpecialtyReportTemplate`.
    ```python
    from canvas_sdk.v1.data import LabReportTemplate
    from logger import log
    def inspect_lab_template(template_name) -> None:
        template = (
            LabReportTemplate.objects
            .active()
            .filter(name=template_name)
            .first()
        )
        if not template:
            log.info(f"No template found with name {template_name}")
            return
        log.info(f"Template: {template.name} (dbid={template.dbid})")
        for field in template.fields.all().order_by("sequence"):
            log.info(
                f"  {field.label}",
                type=field.type,
                units=field.units,
                required=field.required,
                code=field.code,
                code_system=field.code_system,
            )
            for opt in field.options.all():
                log.info(f"    option", key=opt.key, label=opt.label)
    ```
Sample output for a CBC Panel template:
    ```text
    Template: CBC Panel (dbid=42)
      Hemoglobin   type=float  units=g/dL    required=True   code=718-7   code_system=LOINC
      WBC          type=float  units=10^3/uL required=True   code=6690-2  code_system=LOINC
      Differential type=select units=        required=False  code=        code_system=
        option key=NORMAL    label=Normal
        option key=ABNORMAL  label=Abnormal
    ```
With that you can see which labels go into the `fields` dict's keys, what `unit` value to send, and — for `select`/`radio` fields — which `option.key` values are valid for the `value` field.
###  Example 
An example of prefilling a Lab Report. The plugin looks up the CBC Panel template via the [`LabReportTemplate`](/sdk/data-lab-report-template/) SDK data module, then iterates the template's `fields` relation to discover which labels and units are available before filling in the values it extracted from the document.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.data_integration import PrefillDocumentFields
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.v1.data import LabReportTemplate
    class PrefillLabReportHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.DOCUMENT_RECEIVED)
        def compute(self) -> list[Effect]:
            cbc = (
                LabReportTemplate.objects
                .active()
                .filter(name__icontains="CBC")
                .first()
            )
            if not cbc:
                return []
            # Values the plugin extracted from the document via OCR/LLM.
            # Hardcoded here for clarity.
            extracted = {
                "Hemoglobin": ("13.5", False),
                "WBC": ("11.2", True),
            }
            # Iterate the template's fields to discover which labels and units
            # the template defines, then fill in only the ones the plugin has
            # values for. Each field exposes `label`, `units`, `type`, and
            # `required` (see LabReportTemplateField).
            prefill_fields: dict[str, dict] = {}
            for field in cbc.fields.all():
                if field.label not in extracted:
                    continue
                value, abnormal = extracted[field.label]
                prefill_fields[field.label] = {
                    "value": value,
                    "unit": field.units or "",
                    "abnormal": abnormal,
                }
            return [
                PrefillDocumentFields(
                    document_id=self.event.target.id,
                    templates=[
                        {
                            "template_id": cbc.dbid,
                            "template_name": cbc.name,
                            "fields": prefill_fields,
                        },
                    ],
                    annotations=[
                        {"text": "Prefilled via AI", "color": "#FF9800"},
                    ],
                ).apply()
            ]
    ```
##  Removing a Document from a Patient 
To unlink a document from its currently-linked patient in the Data Integration queue, import the `RemoveDocumentFromPatient` class and create an instance of it. An IntegrationTask carries at most one patient link at a time, so this effect simply clears that link.
Attribute |  | Type | Description  
---|---|---|---  
`document_id` | required | string | The `id` of the [IntegrationTask](/sdk/data-integration-task/#integrationtask) document to unlink from its patient.  
An example of unlinking a document from its patient:
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.data_integration import RemoveDocumentFromPatient
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class RemoveDocumentHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.DOCUMENT_LINKED_TO_PATIENT)
        def compute(self) -> list[Effect]:
            return [
                RemoveDocumentFromPatient(
                    document_id=self.event.target.id,
                ).apply()
            ]
    ```
##  Annotations 
The `annotations` field on any data integration effect accepts a list of dictionaries with the following keys:
Key | Type | Description  
---|---|---  
`text` | string | The annotation text to display (e.g., "AI 95%").  
`color` | string | Hex color code (e.g., "#4CAF50" for green).  
----- END PAGE https://docs.canvasmedical.com/sdk/effect-data-integration/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-event-validation-error/
##  Overview 
The `EventValidationError` effect is used to block the creation of an event (such as a NoteStateChangeEvent create) when custom validation fails. If this effect is returned by a protocol in response to an event (e.g., `NOTE_STATE_CHANGE_EVENT_PRE_CREATE`), the event is aborted and the provided error message is surfaced to the user.
##  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
errors | list[ValidationError] | List of validation errors to display to the user. | Yes  
###  ValidationError dataclass 
Each item in the `errors` list is a `ValidationError` dataclass with the following fields:
Field | Type | Description  
---|---|---  
message | string | The error message to display to the user.  
##  Example Usage 
Return an `EventValidationError` from your protocol's `compute` method to block the event and show a message to the user. You can also return other effects alongside `EventValidationError`.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.v1.data import Note
    from canvas_sdk.v1.data.coverage import CoverageStack
    from canvas_sdk.effects.validation import EventValidationError, ValidationError
    from canvas_sdk.effects.banner_alert import AddBannerAlert, RemoveBannerAlert
    class MyHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.NOTE_STATE_CHANGE_EVENT_PRE_CREATE)
        def handle_no_coverage(self, patient_id: str, note: Note) -> list[Effect]:
            """If the patient has no coverage, add banner alert and do not allow notes to be locked or charges pushed."""
            if note.patient.coverages.filter(stack=CoverageStack.IN_USE).count() == 0:
                return [
                    EventValidationError(
                        errors=[
                            ValidationError(
                                message="Patient has no coverage. Do not send claim to billing department until coverage(s) have been added."
                            )
                        ]
                    ).apply(),
                    AddBannerAlert(
                        patient_id=patient_id,
                        key="no_coverage",
                        narrative="Patient has no documented coverages.",
                        placement=[
                            AddBannerAlert.Placement.CHART,
                            AddBannerAlert.Placement.APPOINTMENT_CARD,
                        ],
                        intent=AddBannerAlert.Intent.ALERT,
                    ).apply(),
                ]
            return [RemoveBannerAlert(patient_id=patient_id, key="no_coverage").apply()]
        def handle_no_billing_line_items(self, note: Note) -> Effect | None:
            """If the note has no billing line items, do not allow notes to be locked or charges pushed."""
            if note.billing_line_items.filter(status="active").count() > 0:
                return None
            val_effect = EventValidationError()
            val_effect.add_error(
                "Cannot lock or push charges for a note with no billing line items."
            )
            return val_effect.apply()
        def compute(self) -> list[Effect]:
            state = self.event.context["state"]
            if state not in ["PSH", "LKD"]:
                return []
            note = Note.objects.get(id=self.event.context["note_id"])
            patient_id = str(note.patient.id)
            effects = self.handle_no_coverage(patient_id=patient_id, note=note)
            if v := self.handle_no_billing_line_items(note=note):
                effects.append(v)
            return effects
    ```
##  Supported Events 
Event | Behavior  
---|---  
[`NOTE_STATE_CHANGE_EVENT_PRE_CREATE`](/sdk/events/#notes) | Blocks the note state change (e.g. lock, push charges) and displays errors in the UI.  
[`APPOINTMENT__FORM__UPDATED`](/sdk/events/#appointments) | Disables the Book button on the appointment scheduling modal and displays errors as a tooltip.  
##  Implementation Details 
  - If an `EventValidationError` is returned, the event is aborted and the error message is shown in the UI (if initiated from the UI).
  - This effect is typically used for pre-create validation of events, such as note state changes or appointment scheduling.
  - Any other effects returned alongside an `EventValidationError` are still applied, even though the event itself is blocked. In the example above, the `AddBannerAlert` effect is added and persists even when the note state change is rejected.
----- END PAGE https://docs.canvasmedical.com/sdk/effect-event-validation-error/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-external-event/
The `ExternalEvent` effect provides a way to create and update external clinical events within the Canvas platform. External events represent clinical encounters from external data sources such as ADT (Admission, Discharge, Transfer) feeds, enabling tracking of patient visits that occur outside of Canvas.
##  Attributes 
Name | Type | Description  
---|---|---  
`external_event_id` | `str` or `UUID` or `None` | Unique identifier of an existing external event. Must be unset when creating; required when updating.  
`patient_id` | `str` or `None` | ID of the patient for this event. Required when creating.  
`visit_identifier` | `str` or `None` | Identifier for the visit/encounter. Required when creating.  
`message_control_id` | `str` or `None` | Unique identifier for the message (e.g., HL7 message control ID). Required when creating.  
`event_type` | `str` or `None` | Type of event (e.g., "ADT^A01" for admission). Required when creating.  
`event_datetime` | `datetime` or `None` | Date and time when the event occurred.  
`event_cancelation_datetime` | `datetime` or `None` | Date and time when the event was cancelled. Set this to mark an event as cancelled.  
`message_datetime` | `datetime` or `None` | Date and time when the message was sent.  
`information_source` | `str` or `None` | Source of the event information (e.g., hospital name, system name).  
`facility_name` | `str` or `None` | Name of the facility where the event occurred.  
`raw_message` | `str` or `None` | Raw message content (e.g., original HL7 message).  
##  Validation & Errors 
Before any effect is emitted, the model runs these checks:
###  Create Validation 
  - **external_event_id** must **not** be set (will be generated by the system)
  - **patient_id** is **required**
  - **visit_identifier** is **required**
  - **message_control_id** is **required**
  - **event_type** is **required**
###  Update Validation 
  - **external_event_id** is **required** and must reference an existing external event
  - All other fields are optional; only dirty (modified) fields are updated
##  Effect Methods 
###  `create()`
Create a new external event record.
  - **Effect Type:** `CREATE_EXTERNAL_EVENT`
  - **Payload:** `{ "data": { patient_id, visit_identifier, message_control_id, event_type, ... } }`
###  `update()`
Update an existing external event.
  - **Effect Type:** `UPDATE_EXTERNAL_EVENT`
  - **Payload:** `{ "data": { external_event_id, <dirty_fields> } }`
  - Only fields marked dirty (modified on the model) are included in the update.
##  Example Usage 
    ```python
    from datetime import datetime
    from canvas_sdk.effects.external_event import ExternalEvent
    from canvas_sdk.v1.data.external_event import ExternalEvent as ExternalEventModel
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.first()
    ```
###  Create an Admission Event 
    ```python
    # Create an external event for a hospital admission
    admission_event = ExternalEvent(
        patient_id=str(patient.id),
        visit_identifier="VISIT-2024-001234",
        message_control_id="MSG-20240115-143052",
        event_type="ADT^A01",  # Admission
        event_datetime=datetime.now(),
        message_datetime=datetime.now(),
        information_source="General Hospital ADT Feed",
        facility_name="General Hospital - Main Campus",
        raw_message="MSH|^~\\&|HOSPITAL|FAC|CANVAS|...",
    )
    effect_create = admission_event.create()
    ```
###  Create a Discharge Event 
    ```python
    # Create an external event for a discharge
    discharge_event = ExternalEvent(
        patient_id=str(patient.id),
        visit_identifier="VISIT-2024-001234",  # Same visit as admission
        message_control_id="MSG-20240118-091530",
        event_type="ADT^A03",  # Discharge
        event_datetime=datetime.now(),
        message_datetime=datetime.now(),
        information_source="General Hospital ADT Feed",
        facility_name="General Hospital - Main Campus",
    )
    effect_discharge = discharge_event.create()
    ```
###  Cancel an Existing Event 
    ```python
    # Find an existing external event to cancel
    existing_event = ExternalEventModel.objects.filter(
        patient__id=patient.id,
        event_cancelation_datetime__isnull=True,  # Not already cancelled
    ).first()
    if existing_event:
        # Cancel the event by setting the cancelation datetime
        cancel_effect = ExternalEvent(
            external_event_id=str(existing_event.id),
            event_cancelation_datetime=datetime.now(),
        )
        effect_cancel = cancel_effect.update()
    ```
###  Update Event Details 
    ```python
    # Update an existing external event with additional information
    existing_event = ExternalEventModel.objects.filter(patient__id=patient.id).first()
    if existing_event:
        updated_event = ExternalEvent(
            external_event_id=str(existing_event.id),
            facility_name="General Hospital - West Wing (Corrected)",
            raw_message="MSH|^~\\&|HOSPITAL|FAC|CANVAS|...|CORRECTED",
        )
        effect_update = updated_event.update()
    ```
###  Create Event with All Fields 
    ```python
    # Create an external event with all optional fields populated
    complete_event = ExternalEvent(
        # Required fields
        patient_id=str(patient.id),
        visit_identifier="VISIT-2024-005678",
        message_control_id="MSG-20240120-163045",
        event_type="ADT^A01",
        # Optional datetime fields
        event_datetime=datetime(2024, 1, 20, 16, 30, 0),
        message_datetime=datetime(2024, 1, 20, 16, 30, 45),
        # Optional string fields
        information_source="Regional Medical Center - HL7 Interface",
        facility_name="Regional Medical Center - Emergency Department",
        raw_message="MSH|^~\\&|RMC|ED|CANVAS|RECV|20240120163045||ADT^A01|MSG123|P|2.5",
    )
    effect_complete = complete_event.create()
    ```
##  Common Event Types 
The `event_type` field typically contains HL7 ADT event codes:
Event Type | Description  
---|---  
ADT^A01 | Admit/Visit Notification  
ADT^A02 | Transfer a Patient  
ADT^A03 | Discharge/End Visit  
ADT^A04 | Register a Patient  
ADT^A08 | Update Patient Information  
ADT^A11 | Cancel Admit/Visit Notification  
ADT^A12 | Cancel Transfer  
ADT^A13 | Cancel Discharge/End Visit  
----- END PAGE https://docs.canvasmedical.com/sdk/effect-external-event/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-health-gorilla-lab-order-ingest/
The `HealthGorillaLabOrderIngest` effect creates a Canvas `LabOrder` plus its `LabTest` rows for an order that was placed outside Canvas — for example, by a partner on its own Health Gorilla tenant. Canvas records the order so it shows up in the chart, but the order's `hg_request_result` field is set to a partner-supplied skip-send marker so Canvas's send-to-Health-Gorilla worker treats the order as already-sent and never forwards it.
Typical use is from a SimpleAPI route the partner POSTs to when they place a standing or recurring order on their side.
##  Behavior 
Canvas processes the effect in a single transaction:
  1. Resolves `Patient` (by `key`), `Staff` (by NPI), and `Note` (by external id).
  2. Creates a `LabOrder` with `hg_request_result` set non-empty so the send-to-HG worker leaves it alone.
  3. Creates one `LabTest` row per HG order code in `test_codes`. Test names are filled from the HG ontology when available.
Each `LabTest` is created with status `RECEIVED`, since externally-originated orders are past the Canvas send pipeline.
##  Attributes 
Attribute | Type | Required | Description  
---|---|---|---  
patient_id | str | yes | Canvas Patient `key` (uuid).  
ordering_provider_npi | str | yes | NPI used to look up the Staff record.  
note_id | str | yes | Canvas Note `externally_exposable_id` (uuid). LabOrders require an associated Note.  
ontology_lab_partner | str | yes | Ontology lab partner name (e.g. `"Quest Diagnostics"`, `"LabCorp"`).  
date_ordered | datetime | yes | When the order was authored on the partner side.  
hg_request_result | str | yes | Skip-send marker. Convention is the partner's HG `RequestGroup` URL or id so Canvas can correlate later. Any non-empty value works.  
test_codes | list[str] | yes | One or more HG order codes. One `LabTest` row is created per entry.  
external_id | str | no | Stored on `LabOrder.healthgorilla_id`; useful for partner-side dedup.  
comment | str | no | Stored on `LabOrder.comment`.  
##  Example 
    ```python
    from datetime import datetime, UTC
    from canvas_sdk.effects.lab_order import HealthGorillaLabOrderIngest
    from canvas_sdk.effects.simple_api import JSONResponse, Response
    from canvas_sdk.handlers.simple_api import APIKeyCredentials, SimpleAPIRoute
    class StandingLabOrdersAPI(SimpleAPIRoute):
        PATH = "/standing-lab-orders"
        def authenticate(self, credentials: APIKeyCredentials) -> bool:
            return credentials.key == self.secrets["ingest-api-key"]
        def post(self) -> list[Response]:
            body = self.request.json()
            return [
                HealthGorillaLabOrderIngest(
                    patient_id=body["patient_id"],
                    ordering_provider_npi=body["ordering_provider_npi"],
                    note_id=body["note_id"],
                    ontology_lab_partner=body["ontology_lab_partner"],
                    date_ordered=datetime.fromisoformat(body["date_ordered"]),
                    hg_request_result=body["hg_request_result"],
                    test_codes=body["test_codes"],
                    external_id=body.get("external_id", ""),
                ).apply(),
                JSONResponse({"external_id": body["external_id"]}, status_code=201),
            ]
    ```
##  Related 
  - [`HealthGorillaLabReportIngest`](/sdk/effects/) — the matching effect for inbound lab reports
  - [`HealthGorillaLabOrderOverride`](/sdk/effects/) — for orders Canvas itself sends to HG
----- END PAGE https://docs.canvasmedical.com/sdk/effect-health-gorilla-lab-order-ingest/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-health-gorilla-lab-order-override/
The `HealthGorillaLabOrderOverride` effect lets a plugin inject FHIR-shaped values (account numbers, bill-to, performer organization, sub-tenant, location) into Canvas's outbound Health Gorilla lab-order payload at order-send time. It is returned from a handler of the [`LAB_ORDER_COMMAND__PRE_SEND`](/sdk/events/) event, which fires from Canvas right before the FHIR `RequestGroup` is built and POSTed to Health Gorilla.
This is the supported way to drive lab-order routing from plugin-owned state (for example, a partner-specific lab account selected in a custom chart UI) without putting any partner/tenant concept into Canvas core.
##  Behavior 
Each field on the effect is independently optional. A field set to a non-`None` value overrides Canvas's default resolution for that field; a field left as `None` (the default) means **no override** — Canvas falls through to its existing resolution path for that field.
Multiple plugins may return overrides for the same order. When two effects set the same field, the later one wins.
##  Attributes 
Attribute | Type | Description  
---|---|---  
practitioner_account_number | str | Stamped on the contained `Practitioner.identifier` (Account Number).  
organizational_account_number | str | Stamped on the contained authorizing `Organization.identifier` (Account Number).  
hg_organization_id | str | Health Gorilla facility id (`f-...`) used for the `requestgroup-performer` reference. Skips the lab-name catalog lookup.  
hg_tenant_id | str | Health Gorilla sub-tenant Organization id. With `hg_tenant_id` alone, adds a `requestgroup-authorizedBy` reference to `Organization/t-{tenant_id}`. With both `hg_tenant_id` and `hg_location_id` set, the reference is `Organization/tl-{tenant_id}-{location_id}` (the HG sub-tenant location form).  
hg_location_id | str | Health Gorilla tenant-location id. Combined with `hg_tenant_id` to produce a `tl-` `requestgroup-authorizedBy` reference. Has no effect on its own.  
hg_practitioner_id | str | Health Gorilla Practitioner id, appended as an additional identifier on the contained Practitioner with system `https://www.healthgorilla.com`. Use when the ordering provider is registered with HG and you want to surface that registration on the outbound order alongside the NPI.  
bill_to_code | BillToCode | Explicit `Account.type` coding. Overrides the existing coverage-derived inference.  
###  BillToCode 
`bill_to_code` is constrained to one of four Health-Gorilla–recognized codes. It is a Pydantic `Literal`, so attempting to construct the effect with any other value raises a `ValidationError` at construction time.
Value | HG `Account.type.coding.code` | Display  
---|---|---  
`self` | `self` | Client  
`patient` | `patient` | Patient  
`guarantor` | `guarantor` | Guarantor  
`thirdParty` | `thirdParty` | Third Party  
When `bill_to_code` is `patient` or `guarantor`, Canvas attaches the appropriate `guarantor` reference to the FHIR `Account` automatically.
##  Example 
    ```python
    from canvas_generated.messages.events_pb2 import EventType
    from canvas_sdk.effects.lab_order import HealthGorillaLabOrderOverride
    from canvas_sdk.handlers import BaseHandler
    class InjectPartnerLabAccount(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.LAB_ORDER_COMMAND__PRE_SEND)
        def compute(self):
            # Resolve the selected lab account from plugin-owned state — for
            # example, a custom-model row keyed off the note's partner_slug
            # stored in note.related_data.
            account = self.resolve_lab_account_for_note(self.event.target)
            if account is None:
                return []
            return [HealthGorillaLabOrderOverride(
                practitioner_account_number=account.practitioner_account_number,
                organizational_account_number=account.organizational_account_number,
                hg_organization_id=account.hg_organization_id,
                hg_tenant_id=account.tenant_id or None,
                hg_location_id=account.location_id or None,
                bill_to_code="self",
            ).apply()]
    ```
##  Related 
  - [`LAB_ORDER_COMMAND__PRE_SEND`](/sdk/events/) event — fires this effect's host
  - [Health Gorilla multi-tenant Organization model](https://developer.healthgorilla.com/docs/organization-2)
  - [HG `RequestGroup` profile](https://developer.healthgorilla.com/docs/requestgroup)
----- END PAGE https://docs.canvasmedical.com/sdk/effect-health-gorilla-lab-order-override/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-health-gorilla-lab-report-ingest/
The `HealthGorillaLabReportIngest` effect creates a Canvas `LabReport` plus its `LabValue` rows and attaches a PDF for a result that was received outside Canvas — for example, by a partner on its own Health Gorilla tenant or any upstream lab interface. It is the inbound counterpart to [`HealthGorillaLabOrderIngest`](/sdk/effects/).
Canvas's ingest path is equivalent to a standard Health Gorilla pull, minus the HG fetch — the partner provides the PDF and Canvas stores it through the existing inbound-lab pipeline.
##  Dedup 
Reports are deduped server-side on `(external_id, version)`:
State | Result  
---|---  
New `external_id` | Create new `LabReport`.  
Same `external_id`, higher `version` | Replace values, bump version on existing record, re-store PDF.  
Same `external_id`, same or lower `version` | No-op; existing record kept, no PDF fetch.  
This matches Canvas's standard inbound-lab version-bump contract, so partners can safely replay events.
##  PDF handling 
Exactly one of `pdf_url` or `pdf_base64` must be set. Canvas fetches the PDF (or decodes the inline base64) **outside** the `LabReport` transaction so a slow partner bucket cannot hold row locks. Limits:
  - `pdf_base64` capped at 1 MB encoded (~750 KB binary). Use `pdf_url` for larger PDFs.
  - `pdf_url` fetch capped at 10 MB.
##  Attributes 
Attribute | Type | Required | Description  
---|---|---|---  
lab_order_id | str | yes | Canvas LabOrder `externally_exposable_id` (uuid) the report belongs to.  
patient_id | str | yes | Canvas Patient `key` (uuid).  
external_id | str | yes | Partner-side report id. Used for dedup.  
version | int | yes | Report version, monotonically increasing per `external_id`. Must be `>= 1`.  
status | str | no | Report status; defaults to `"final"`.  
date_performed | datetime | yes | When the report was generated on the partner side.  
lab_values | list[dict] | yes | Per-test results. Each entry: `ontology_test_code`, `ontology_test_name`, `value`, `units`, `reference_range`, `abnormal_flag`, `observation_status`, `comment`.  
pdf_url | str | one of | URL Canvas will GET the PDF from (typically a partner's presigned URL).  
pdf_base64 | str | one of | PDF bytes inline as base64. Use only for small PDFs.  
##  Example 
    ```python
    from datetime import datetime, UTC
    from canvas_sdk.effects.lab_order import HealthGorillaLabReportIngest
    from canvas_sdk.effects.simple_api import JSONResponse, Response
    from canvas_sdk.handlers.simple_api import APIKeyCredentials, SimpleAPIRoute
    class ExternalLabReportsAPI(SimpleAPIRoute):
        PATH = "/external-lab-reports"
        def authenticate(self, credentials: APIKeyCredentials) -> bool:
            return credentials.key == self.secrets["ingest-api-key"]
        def post(self) -> list[Response]:
            body = self.request.json()
            return [
                HealthGorillaLabReportIngest(
                    lab_order_id=body["lab_order_id"],
                    patient_id=body["patient_id"],
                    external_id=body["external_id"],
                    version=body.get("version", 1),
                    date_performed=datetime.fromisoformat(body["date_performed"]),
                    lab_values=body["lab_values"],
                    pdf_url=body["pdf_url"],
                ).apply(),
                JSONResponse(
                    {"external_id": body["external_id"], "version": body.get("version", 1)},
                    status_code=202,
                ),
            ]
    ```
##  Related 
  - [`HealthGorillaLabOrderIngest`](/sdk/effects/) — the matching effect for inbound lab orders
----- END PAGE https://docs.canvasmedical.com/sdk/effect-health-gorilla-lab-report-ingest/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-http-request/
The `HttpRequestEffect` lets a plugin ask the Canvas platform to issue an HTTP request on its behalf. The plugin returns the effect from a handler and the platform performs the call.
We recommend running the request asynchronously by chaining `.set_async(...)` onto the applied effect. The request is then handed off to the platform's async runner, which manages delay, retries, and backoff so the handler doesn't block on the network. Setting `retry_on_status_codes` automatically opts into async execution (equivalent to `.set_async(delay_seconds=0)`), so you only need to call `.set_async(...)` explicitly to add a delay or set `max_retries`.
Without `.set_async(...)` and without `retry_on_status_codes`, the effect is executed inline.
##  Attributes 
Name | Type | Required | Description  
---|---|---|---  
`url` | `str` | Yes | The URL to request. Must be non-empty. Cannot resolve to a private or loopback address (see Security).  
`method` | `HttpMethod` | No | The HTTP method to use. Defaults to `HttpMethod.GET`.  
`headers` | `dict[str, str]` or `None` | No | Request headers. Header values are transmitted as-is — store credentials in the plugin's [`secrets`](/sdk/secrets/) and reference them here rather than hard-coding them.  
`body` | `str` or `None` | No | The request body, as a string. For JSON payloads, serialize with `json.dumps(...)` and set the appropriate `Content-Type` header.  
`retry_on_status_codes` | `list[int]` or `None` | No | HTTP status codes that should trigger a retry. Each value must be in the range `100`–`599`. Setting this automatically routes the request through the async runner (equivalent to `.set_async(delay_seconds=0)`); use `.set_async(...)` only to override the delay or set `max_retries`.  
##  `HttpMethod`
A `StrEnum` of the supported HTTP methods. You can also pass the string value as well.
Member | Value  
---|---  
`HttpMethod.GET` | `"GET"`  
`HttpMethod.POST` | `"POST"`  
`HttpMethod.PUT` | `"PUT"`  
`HttpMethod.PATCH` | `"PATCH"`  
`HttpMethod.DELETE` | `"DELETE"`  
##  Security & Network Behavior 
The platform applies several safeguards before executing the request:
  - **SSRF protection.** The host is resolved and rejected if it points at a private (RFC 1918), loopback, link-local (including the `169.254.169.254` cloud metadata address), multicast, reserved, or unspecified address. Both literal IPs (e.g. `http://10.0.0.1/`) and hostnames that resolve to such addresses are blocked.
  - **Redirect behavior.** GET requests follow redirects normally. Non-GET methods (POST, PUT, PATCH, DELETE) are made with `allow_redirects=False`, so a `3xx` response is returned as-is rather than re-posting data to a different host.
  - **Request timeout.** Each request has a 30-second timeout. Requests that exceed it are aborted.
  - **Connection errors are swallowed.** If the upstream service is unreachable or the request fails at the transport layer, the failure is logged and the effect pipeline continues — it does not raise back into your handler.
  - **Redacted logging.** The platform logs the request method, host, path, and final status code. Query strings, fragments, request headers, request bodies, and response bodies are never logged, so credentials passed via query string or `Authorization` headers do not leak into platform logs.
##  Async Execution 
Chain `.set_async(...)` onto the result of `.apply()` to have the platform schedule the request through its async runner instead of running it inline with the handler. You can read more about async effect execution [here](/sdk/effects/#async-execution).
When `retry_on_status_codes` is set on the effect, the SDK automatically sets `delay_seconds=0` (async-now) so the request is routed through the async runner — you don't need to call `.set_async()` separately just for retries. The async runner uses `retry_on_status_codes` (in combination with `max_retries`) to decide whether a response should trigger a retry.
##  Handling Credentials 
Header values are transmitted to the upstream service exactly as provided. Do not hard-code API tokens or other credentials into your plugin source. Instead, declare them as [secrets](/sdk/secrets/) in `CANVAS_MANIFEST.json` and read them at runtime via `self.secrets` on the handler.
##  Example Usage 
###  Send a POST request asynchronously 
    ```python
    import json
    from canvas_sdk.events import EventType
    from canvas_sdk.effects.http_request import HttpMethod, HttpRequestEffect
    from canvas_sdk.handlers import BaseHandler
    class MyHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.PATIENT_CREATED)
        def compute(self):
            http_effect = HttpRequestEffect(
                url="https://api.example.com/submit",
                method=HttpMethod.POST,
                headers={
                    "Authorization": f"Bearer {self.secrets['MY_API_TOKEN']}",
                    "Content-Type": "application/json",
                },
                body=json.dumps({"patient_id": self.target}),
                retry_on_status_codes=[500, 502, 503],
            )
            # retry_on_status_codes already implies async-now; .set_async() is here to add max_retries
            return [http_effect.apply().set_async(max_retries=3)]
    ```
###  Issue a simple GET request 
    ```python
    http_effect = HttpRequestEffect(
        url="https://api.example.com/status",
    )
    return [http_effect.apply().set_async(delay_seconds=0)]
    ```
###  Schedule a delayed request 
    ```python
    # Run the request 60 seconds from now
    http_effect = HttpRequestEffect(
        url="https://api.example.com/sync",
        method=HttpMethod.PUT,
        body=json.dumps({"status": "ready"}),
        headers={"Content-Type": "application/json"},
    )
    return [http_effect.apply().set_async(delay_seconds=60)]
    ```
##  Validation 
Construction is validated by Pydantic and will raise a `ValidationError` for:
  - An empty `url`.
  - A `method` that is not a member of `HttpMethod`.
  - A `retry_on_status_codes` entry that is not an integer or falls outside the `100`–`599` range.
----- END PAGE https://docs.canvasmedical.com/sdk/effect-http-request/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-lab-report/
The `LabReport` effect lets plugins manage a lab report's full lifecycle, independently of any lab order. It is designed for workflows where a report exists before its structured results do — for example, a scanned report that arrives by fax or upload and is OCR'd asynchronously, so the lab tests and values aren't ready until hours or days after the report is created.
With these effects a plugin can:
  - **Create** a report up front, with no order, no PDF, and no results.
  - **Attach results** (lab tests and values) to that report later, as they become available.
  - **Update** report metadata, such as its name.
  - **Enter-in-error** a report so a user can self-correct a mistake.
A created report is linked to the patient you supply. It starts **empty** — until you attach results it holds no tests or values, and it stays an **uncommitted draft**. A results-less draft does **not** appear in the patient's lab reports in the chart (that view only shows committed reports); it exists but isn't surfaced there yet. The first `attach_results()` commits the report — that's when it fills in, creates the observations behind its values, and appears in the chart, reading like any other lab report. It is **not** a Data Integration document, so it never appears in the Data Integration queue, and Canvas creates the report's diagnostic report and renders a document from its data automatically.
##  Identifying a report 
Every effect references a report by one of two handles:
Handle | What it is | When to use it  
---|---|---  
`reference_id` | A stable identifier your plugin assigns when it creates a report. | The report your plugin created. Required on `create()`.  
`report_id` | The [LabReport](/sdk/data-labs/)'s `id`. | Any report — including ones your plugin didn't create.  
Effects are fire-and-forget, so `create()` does not return the new report's `report_id`. Use the `reference_id` you assigned as your handle for the later `attach`/`update`/`enter_in_error` calls. If you need the `report_id` (for example to act on a report your plugin did not create), read it from the `LAB_REPORT_CREATED` event or query the [LabReport](/sdk/data-labs/) data model by `reference_id` (the handle you assigned is stored there; the data model's own `external_id` is reserved for electronic/Health-Gorilla feed ids).
Namespace your `reference_id` values (e.g. `"my-plugin:batch-2026-06-17:img-44"`) so they don't collide with report ids from other inbound-lab sources.
##  Attributes 
Name | Type | Description  
---|---|---  
`reference_id` | `str` or `None` | The plugin-assigned handle (maximum 40 characters). **Required** when creating; usable as the handle for other operations.  
`report_id` | `UUID` or `None` | The [LabReport](/sdk/data-labs/)'s `id` (a valid uuid string is also accepted). Must be **unset** when creating; an alternative handle otherwise.  
`patient_id` | `str` or `None` | The [Patient](/sdk/data-patient/)'s `id`. **Required** when creating.  
`report_name` | `str` or `None` | Human-readable report name (maps to the report's document name).  
`date_performed` | `datetime` or `None` | The report's effective/displayed date. If omitted on `create`, it defaults to the creation time — correct it later via `update`.  
##  Methods 
###  create() 
Create a lab report decoupled from its results — no order, no PDF, and no values required.
####  Validation 
  - `reference_id` is **required** (it is your handle for attaching results later).
  - `patient_id` is **required**.
  - `report_id` must **not** be set (creation assigns the id).
  - The `reference_id` must not already be in use by an existing report.
####  Example 
    ```python
    import datetime
    from canvas_sdk.effects.lab_report import LabReport
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.first()
    report = LabReport(
        reference_id="my-plugin:batch-2026-06-17:img-44",
        patient_id=patient.id,
        report_name="CBC (scanned 2026-06-17)",
        date_performed=datetime.datetime.now(),
    )
    effect = report.create()
    ```
###  update() 
Update report metadata, such as renaming it via `report_name`. Only the fields you set are sent. Only `report_name` and `date_performed` can be changed — `update()` **cannot move the report to a different patient** ; the patient is fixed when the report is created. If a report was attached to the wrong patient, enter it in error and recreate it on the correct patient.
####  Validation 
  - Exactly one handle (`reference_id` or `report_id`) is **required**.
  - At least one mutable field (`report_name` or `date_performed`) must be provided.
  - The report must not already be entered-in-error or reviewed by a provider.
####  Example 
    ```python
    from canvas_sdk.effects.lab_report import LabReport
    renamed = LabReport(
        reference_id="my-plugin:batch-2026-06-17:img-44",
        report_name="Complete Blood Count",
    )
    effect = renamed.update()
    ```
###  enter_in_error() 
Flag a report as entered-in-error — use it when a report was filed incorrectly. It junks the report (removing it from active views) and records who entered it in error. The report's observations and its linked DiagnosticReport and DocumentReference records are marked entered-in-error as well. Once a report is entered-in-error (or junked) it can no longer be modified — `update()` and `attach_results()` on it raise a validation error.
####  Validation 
  - A handle (`reference_id` or `report_id`) is **required**.
  - Any other field is ignored (not rejected).
  - The report must not already be entered-in-error or reviewed by a provider.
####  Example 
    ```python
    from canvas_sdk.effects.lab_report import LabReport
    voided = LabReport(reference_id="my-plugin:batch-2026-06-17:img-44")
    effect = voided.enter_in_error()
    ```
##  Attaching results 
Once results are available, attach them with the `attach_results` method on `LabReport`. The report handle comes from the `LabReport` instance (`report_id` or `reference_id`); the method takes a list of `LabTest`s, each grouping the `LabValue`s measured for it — so the values for one test are bundled under that test in the chart. Attaching is **additive** : it appends tests and values without removing any already on the report, and Canvas creates an observation for each value automatically.
Attaching results saves the report and regenerates its linked DiagnosticReport and rendered DocumentReference (the report's document) to reflect the newly attached values. The first `attach_results()` call also commits the report (a never-populated report stays an uncommitted draft). A committed report enters the lab-review workflow **review-required** and **requiring a signature** , but with **no reviewer assigned** — a clinician still has to pick it up, review, and sign it. Once a provider has reviewed it, the report is locked to further SDK edits.
###  Arguments 
Name | Type | Description  
---|---|---  
`lab_tests` | list[`LabTest`] | The tests to attach. At least one is required. The report handle comes from the `LabReport` instance.  
###  `LabTest`
A `LabTest` is a test that was performed — an ordered panel or a single analyte — and it groups its result values (a result test can carry many values). `ontology_test_code`/`ontology_test_name` are the lab's **order/compendium** code and name — _not_ LOINC. LOINC is supplied separately via `codings` (see `CodingData` below).
Name | Type | Description  
---|---|---  
`ontology_test_code` | `str` | The lab's order/compendium code for the test. Defaults to empty string.  
`ontology_test_name` | `str` | Human-readable test name. Defaults to empty string.  
`codings` | list[`CodingData`] or `None` | The test's LOINC coding(s); only LOINC-system codings are stored.  
`values` | list[`LabValue`] | The result values for this test. **At least one is required.**  
###  `LabValue`
Each `LabValue` is one measured result on its test.
Name | Type | Description  
---|---|---  
`value` | `str` | The result value. Required.  
`units` | `str` | Unit of measure (e.g. `"g/dL"`). Defaults to empty string.  
`reference_range` | `str` | Reference range as free text. Defaults to empty string.  
`abnormal_flag` | `AbnormalFlag` or `None` | Flags the value against its reference range. Any non-empty flag marks the result abnormal in the lab report. Defaults to `None`.  
`observation_status` | `ObservationStatus` | Status of the observation. Defaults to `ObservationStatus.FINAL`.  
`comment` | `str` | Free-text comment. Defaults to empty string.  
`codings` | list[`CodingData`] or `None` | The value's LOINC coding(s); only LOINC-system codings are stored.  
###  `CodingData`
A coding attached to a test or a value, reused from the [`Observation`](/sdk/effect-observation/) effect. Only codings whose `system` is `http://loinc.org` are persisted, and the `display` becomes the stored coding name.
Name | Type | Description  
---|---|---  
`code` | `str` | The LOINC code (e.g. `"718-7"`). Required.  
`display` | `str` | Human-readable display; stored as the coding's name.  
`system` | `str` | Coding system URI. Use `"http://loinc.org"`.  
`version` | `str` | Optional coding-system version. Defaults to empty.  
`user_selected` | `bool` | Whether a user selected this coding. Defaults to `False`.  
###  `AbnormalFlag`
A `StrEnum` of abnormal-result flags (HL7 v2 table 0078) for a `LabValue`. Setting any of these marks the result abnormal on the lab report.
Member | Value  
---|---  
`HIGH` | `H`  
`LOW` | `L`  
`CRITICAL_HIGH` | `HH`  
`CRITICAL_LOW` | `LL`  
`BELOW_ABSOLUTE_LOW` | `<`  
`ABOVE_ABSOLUTE_HIGH` | `>`  
`ABNORMAL` | `A`  
`CRITICAL_ABNORMAL` | `AA`  
`SUSCEPTIBLE` | `S`  
`RESISTANT` | `R`  
`INTERMEDIATE` | `I`  
`NEGATIVE` | `NEG`  
`POSITIVE` | `POS`  
###  `ObservationStatus`
A `StrEnum` of statuses for a `LabValue`'s observation. Defaults to `FINAL`.
Member | Value  
---|---  
`FINAL` | `final`  
`PRELIMINARY` | `preliminary`  
`AMENDED` | `amended`  
`CORRECTED` | `corrected`  
`CANCELLED` | `cancelled`  
`REGISTERED` | `registered`  
`ENTERED_IN_ERROR` | `entered-in-error`  
`UNKNOWN` | `unknown`  
####  Validation 
  - Exactly one of `reference_id` or `report_id` is **required**.
  - At least one `LabTest` is **required** , and each `LabTest` requires at least one `LabValue`.
  - The report must exist and must not be entered-in-error or reviewed by a provider.
####  Example 
A SimpleAPI route an OCR service calls once it has abstracted the values:
    ```python
    from canvas_sdk.effects.lab_report import LabReport, LabTest, LabValue
    from canvas_sdk.effects.observation import CodingData
    from canvas_sdk.effects.simple_api import JSONResponse, Response
    from canvas_sdk.handlers.simple_api import APIKeyAuthMixin, SimpleAPIRoute
    LOINC = "http://loinc.org"
    class LabResultsAPI(APIKeyAuthMixin, SimpleAPIRoute):
        PATH = "/lab-results"
        def post(self) -> list[Response]:
            body = self.request.json()
            return [
                LabReport(reference_id=body["reference_id"]).attach_results(
                    [
                        LabTest(
                            ontology_test_code=test.get("order_code", ""),
                            ontology_test_name=test.get("name", ""),
                            codings=(
                                [CodingData(code=test["loinc"], display=test.get("name", ""), system=LOINC)]
                                if test.get("loinc")
                                else None
                            ),
                            values=[
                                LabValue(
                                    value=value["value"],
                                    units=value.get("units", ""),
                                    reference_range=value.get("reference_range", ""),
                                    codings=(
                                        [CodingData(code=value["loinc"], display=value.get("name", ""), system=LOINC)]
                                        if value.get("loinc")
                                        else None
                                    ),
                                )
                                for value in test["values"]
                            ],
                        )
                        for test in body["tests"]
                    ]
                ),
                JSONResponse({"reference_id": body["reference_id"]}, status_code=202),
            ]
    ```
##  Example Workflow 
The four effects compose into the asynchronous OCR workflow:
  1. A scanned report arrives. The plugin calls `LabReport(reference_id=..., patient_id=..., ...).create()`, keying off an `reference_id` it controls.
  2. Days later, the OCR service finishes. The plugin calls `LabReport(reference_id=...).attach_results([LabTest(..., values=[LabValue(...)])])` to attach the abstracted tests and values — the report's observations populate from there.
  3. To fix the report name, the plugin calls `LabReport(reference_id=..., report_name=...).update()`.
  4. If the report was filed in error, the plugin calls `LabReport(reference_id=...).enter_in_error()`.
##  Related 
  - [`Observation`](/sdk/effect-observation/) — create or update individual clinical observations.
----- END PAGE https://docs.canvasmedical.com/sdk/effect-lab-report/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-messages/
The `Message` effect provides a unified way to create, edit, and transmit messages between users (patients or staff) within the Canvas platform. It supports standalone creation, immediate send after creating, edits, and dedicated send operations.
##  Attributes 
Name | Type | Description  
---|---|---  
`message_id` | `str` or `UUID` or `None` | Unique identifier of an existing message. Must be unset when creating a new message; required when editing.  
`content` | `str` or `None` | The text body of the message. Required when creating; cannot be empty.  
`sender_id` | `str` or `UUID` | ID of the user (Patient or Staff) who is sending the message.  
`recipient_id` | `str` or `UUID` | ID of the user (Patient or Staff) who will receive the message.  
`read` | `datetime` or `None` | Timestamp indicating when the message was read by the recipient. Defaults to `None` (unread).  
##  Validation & Errors 
Before any effect is emitted, the model runs these checks:
  - **Sender and Recipient Exist** Verifies that both `sender_id` and `recipient_id` belong to either a `Patient` or a `Staff` record.
  - **Create vs. Edit Constraints**
    - **Create** and **Create-and-Send** must **not** include `message_id`.
    - **Create** and **Create-and-Send** must include non-empty `content` (content cannot be blank or whitespace-only).
    - **Edit** operations **must** include a valid `message_id` that already exists in the database.
##  Caveats 
  - **Role Constraints:** Sender and Recipient must always be one of Patient or Staff. Patient-to-Patient messaging is not allowed.
  - **UI Refresh Required:** Due to system constraints, editing a message requires a manual UI refresh for updated content to display.
  - **No Attachments Supported:** The Message effect does not yet support attachments.
  - **Immediate Post for Patient-to-Staff:** Messages created from a Patient to Staff cannot be drafted and will immediately appear in the timeline. This means that `CREATE_AND_SEND` and `SEND` effects will fail in these scenarios. You should only use the `CREATE` method for Patient-to-Staff messaging.
##  Effect Methods 
###  `create()`
Originate a new message record without sending.
  - **Effect Type:** `CREATE_MESSAGE`
  - **Payload:** `{ "data": { content, sender_id, recipient_id } }`
###  `create_and_send()`
Create the message and immediately send it in one operation.
  - **Effect Type:** `CREATE_AND_SEND_MESSAGE`
  - **Payload:** `{ "data": { content, sender_id, recipient_id } }`
###  `edit()`
Modify an existing message's content.
  - **Effect Type:** `EDIT_MESSAGE`
  - **Payload:** `{ "data": { message_id, content?, sender_id?, recipient_id? } }`
  - Only fields marked dirty (modified on the model) are included; unchanged fields remain intact in the system.
###  `send()`
Send an already-created message. Useful if you separated creation from transmission.
  - **Effect Type:** `SEND_MESSAGE`
  - **Payload:** `{ "data": { message_id } }`
##  Example Usage 
    ```python
    from canvas_sdk.v1.data.message import Message as MessageModel
    from canvas_sdk.v1.data.patient import Patient
    from canvas_sdk.v1.data.staff import Staff
    from canvas_sdk.effects.note.message import Message
    staff = Staff.objects.first()
    patient = Patient.objects.first()
    ```
###  Create (originate) only 
    ```python
    m1 = Message(
        content="Your lab results are available.",
        sender_id=staff.id,
        recipient_id=patient.id
    )
    effect_create = m1.create()
    ```
###  Create and send in one step 
    ```python
    m2 = Message(
        content="Your appointment is confirmed.",
        sender_id=staff.id,
        recipient_id=patient.id
    )
    effect_create_and_send = m2.create_and_send()
    m = MessageModel.objects.get(message_id="msg-1234")
    ```
###  Edit an existing message 
    ```python
    m3 = Message(
        message_id=m.id,
        content="Updated: Your appointment has moved to 3pm."
    )
    effect_edit = m3.edit()
    ```
###  Send an existing message 
    ```python
    m4 = Message(message_id=m.id)
    effect_send = m4.send()
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/effect-messages/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-note-footer-configuration/
The `NoteFooterConfiguration` effect configures the note footer at the note level (rather than per button). Its primary use is hiding Canvas's default state-transition buttons — Lock, Sign, Push charges, Delete, and so on — so that a plugin can supply its own footer buttons in their place, such as with [Note State Action Buttons](/sdk/handlers-action-buttons/#note-state-action-buttons).
Return this effect in response to the `NOTE_FOOTER__GET_CONFIGURATION` event, which fires when a note's footer is loaded. If your handler does not return a configuration, the default state-transition buttons remain visible.
* * *
##  How it works 
As a note's footer loads, Canvas fires `NOTE_FOOTER__GET_CONFIGURATION` targeting that note's external id. A handler subscribed to the event returns a `NoteFooterConfiguration` effect to configure the footer. If no plugin returns one, the footer keeps its default configuration.
###  Event payload 
Property | Value | Description  
---|---|---  
`event.target.id` | `str` (UUID) | The external id of the [Note](/sdk/data-note/#note) whose footer is loading.  
`event.actor` | user | The logged-in user viewing the note, when available.  
`event.context` | `{}` | Empty — no additional context is provided.  
###  Attributes 
Field | Type | Default | Description  
---|---|---|---  
`hide_default_state_buttons` | `bool` | `False` | Hide Canvas's native footer state-transition buttons for this note.  
###  Example 
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.note_footer_configuration import NoteFooterConfiguration
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers.base import BaseHandler
    class HideDefaultStateButtons(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.NOTE_FOOTER__GET_CONFIGURATION)
        def compute(self) -> list[Effect]:
            return [NoteFooterConfiguration(hide_default_state_buttons=True).apply()]
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/effect-note-footer-configuration/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-note-metadata/
The `Note.upsert_metadata` method provides a flexible key-value storage system for note-specific data within the Canvas system. This method enables the creation and updating of custom metadata entries associated with note records, allowing for extensible note information storage beyond standard note fields.
##  Overview 
Note metadata serves as a powerful extension mechanism for storing custom note-related information that doesn't fit within the standard note data model. Metadata is managed through the `upsert_metadata` method on the `Note` effect class.
##  Method 
###  upsert_metadata(key: str, value: str) → Effect 
Creates or updates a metadata entry for the specified note.
####  Parameters 
Parameter | Type | Description | Required  
---|---|---|---  
`key` | `str` | Unique identifier for the metadata entry within the note context | Yes  
`value` | `str` | The metadata value to store | Yes  
####  Prerequisites 
The `Note` effect must be initialized with an `instance_id` corresponding to an existing note.
Attribute | Type | Description | Required  
---|---|---|---  
`instance_id` | `UUID` or `str` | Id of the note record to associate metadata with | Yes  
####  Returns 
An `Effect` object configured for upserting note metadata.
####  Behavior 
  - If a metadata entry with the specified key already exists for the note, it will be updated with the new value
  - If no entry exists, a new metadata entry will be created
  - The operation is idempotent - repeated calls with the same key and value will not create duplicate entries
  - Raises `ValueError` if `instance_id` is not set on the `Note` effect
##  Implementation Details 
###  Validation 
The effect performs comprehensive validation before execution:
  1. **Note Existence Validation** : Verifies that the referenced note exists in the system
  - Queries the note database to confirm the `instance_id` corresponds to an existing note record
  - Returns a descriptive error if the note is not found
  1. **Field Validation** : Ensures all required fields are provided and properly formatted
  - `instance_id` must be set on the `Note` effect
  - Both `key` and `value` must be provided
##  Example Usage 
###  Basic Usage 
    ```python
    from canvas_sdk.effects.note.note import Note
    # Create a metadata entry for note tracking
    note = Note(instance_id="803ce56a-350e-49a4-abae-019d9f5f24b2")
    effect = note.upsert_metadata(key="my_plugin:external_system_id", value="EXT-12345")
    ```
###  Example 
    ```python
    import json
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.note.note import Note
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class NoteMetadataHandler(BaseHandler):
      """
      Adds metadata to notes when a plan command is updated.
      """
      RESPONDS_TO = EventType.Name(EventType.PLAN_COMMAND__POST_UPDATE)
      def compute(self) -> list[Effect]:
        note_id = self.event.context["note"]["id"]
        command_id = self.event.target.id
        note = Note(instance_id=note_id)
        return [note.upsert_metadata(key="my_plugin:last_plan_update_command", value=str(command_id))]
    ```
###  Storing Multiple Metadata Entries 
    ```python
    import json
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.note.note import Note
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class SigningMetadataHandler(BaseHandler):
      """
      Adds metadata to notes when they are signed.
      """
      RESPONDS_TO = EventType.Name(EventType.NOTE_STATE_CHANGE_EVENT_CREATED)
      def compute(self) -> list[Effect]:
        note_id = self.event.context["note"]["id"]
        state = self.event.context.get("note_state_change_event", {}).get("state")
        effects: list[Effect] = []
        if state == "SGN":
          note = Note(instance_id=note_id)
          # Store signing source
          effects.append(note.upsert_metadata(key="my_plugin:signing_source", value="protocol"))
          # Store additional context as JSON
          context_data = {
            "signed_by": self.event.context.get("actor", {}).get("id"),
            "protocol_version": "1.0"
          }
          effects.append(note.upsert_metadata(key="my_plugin:signing_context", value=json.dumps(context_data)))
        return effects
    ```
##  Best Practices 
###  Key Naming Conventions 
  1. **Use Descriptive Names** : Choose keys that clearly indicate the purpose of the metadata
  - Good: `external_system_id`, `workflow_stage`, `signing_source`
  - Avoid: `data1`, `temp`, `misc`
  1. **Namespace Your Keys** : Prefix keys with your plugin name to avoid collisions with other plugins
  - Example: `my_plugin:external_system_id`, `my_plugin:workflow_stage`, `my_plugin:signing_source`
###  Value Storage 
  1. **String Serialization** : All values are stored as strings. For complex data types: 
         ```python
         import json
         from canvas_sdk.effects.note.note import Note
         note = Note(instance_id="803ce56a-350e-49a4-abae-019d9f5f24b2")
         complex_data = {"stage": "review", "approvers": ["user1", "user2"], "timestamp": "2025-01-15T10:30:00Z"}
         note.upsert_metadata(key="my_plugin:workflow_state", value=json.dumps(complex_data))
         ```
  2. **Boolean Values** : Store as "true" or "false" strings for consistency 
         ```python
         from canvas_sdk.effects.note.note import Note
         needs_followup = True
         note = Note(instance_id="803ce56a-350e-49a4-abae-019d9f5f24b2")
         note.upsert_metadata(key="my_plugin:requires_followup", value="true" if needs_followup else "false")
         ```
##  Notes 
  - Metadata entries are note-specific and isolated - the same key can have different values for different notes
  - There is no built-in versioning; updating a key overwrites the previous value
  - The system does not enforce any schema on metadata values - validation is the responsibility of the implementing code
----- END PAGE https://docs.canvasmedical.com/sdk/effect-note-metadata/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-note-restrictions/
The `NoteRestrictionsEffect` and `NoteRestrictionsUpdatedEffect` allow plugins to restrict access to notes and push real-time permission updates to connected clients.
  - **`NoteRestrictionsEffect`** — returned by a plugin in response to a `GET_NOTE_RESTRICTIONS` event. It tells the Canvas UI whether the requesting user should see a banner, have the note content blurred, or have editing disabled.
  - **`NoteRestrictionsUpdatedEffect`** — emitted by a plugin at any time to signal that restrictions for a specific note have changed, causing all connected clients viewing that note to immediately refetch their restrictions.
* * *
##  NoteRestrictionsEffect 
###  How it works 
Every time a note is opened (or its restrictions are refetched), Canvas fires a `GET_NOTE_RESTRICTIONS` event targeting that note's external ID. Plugins that subscribe to this event can return a `NoteRestrictionsEffect` to control what the user sees.
If no plugin returns a `NoteRestrictionsEffect`, the note is unrestricted by default.
###  Event payload 
Property | Value | Description  
---|---|---  
`event.target.id` | `str` (UUID) | The `id` of the note being accessed. Use this to look up the note or its metadata.  
`event.actor.id` | `str` (int) | The database ID of the authenticated user requesting the note. Use `Staff.objects.filter(user__dbid=event.actor.id)` to resolve to a staff record.  
`event.context` | `{}` | Empty — no additional context is provided.  
###  Attributes 
Field | Type | Default | Description  
---|---|---|---  
`restrict_access` | `bool` | `False` | Whether the requesting user is restricted from editing this note.  
`blur_content` | `bool` | `False` | Whether the note body should be blurred for the requesting user.  
`banner_message` | `str` | `None` | `None` | Message shown in the warning banner at the top of the note. If `None`, a default "This note is currently restricted." message is displayed.  
###  Example 
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.note.restrictions import NoteRestrictionsEffect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class NoteAccessHandler(BaseHandler):
        """Restrict access to notes based on custom business logic."""
        RESPONDS_TO = EventType.Name(EventType.GET_NOTE_RESTRICTIONS)
        def compute(self) -> list[Effect]:
            note_id = self.event.target.id
            actor_id = self.event.actor.id
            if not self.user_can_access(note_id, actor_id):
                return [
                    NoteRestrictionsEffect(
                        restrict_access=True,
                        blur_content=True,
                        banner_message="You do not have permission to view this note.",
                    ).apply()
                ]
            return []
        def user_can_access(self, note_id: str, actor_id: str) -> bool:
            # Custom access logic here
            ...
    ```
* * *
##  NoteRestrictionsUpdatedEffect 
###  How it works 
When a plugin performs an action that changes whether a note is restricted (e.g. writing an edit lock to `NoteMetadata`, updating an access rule), it can emit a `NoteRestrictionsUpdatedEffect`. Canvas will broadcast a WebSocket notification to all clients currently viewing that note, causing them to refetch their restrictions immediately — no page reload required.
###  Attributes 
Field | Type | Description  
---|---|---  
`note_id` | `str` (UUID) | The id of the note whose restrictions have changed.  
###  Example 
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.note.restrictions import NoteRestrictionsUpdatedEffect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.v1.data import NoteMetadata
    class NoteAccessChangedHandler(BaseHandler):
        """Broadcast a real-time restriction update after note metadata changes."""
        RESPONDS_TO = EventType.Name(EventType.NOTE_METADATA_UPDATED)
        def compute(self) -> list[Effect]:
            note_id = (
                NoteMetadata.objects
                .filter(id=self.event.target.id)
                .values_list("note__id", flat=True)
                .first()
            )
            if not note_id:
                return []
            return [NoteRestrictionsUpdatedEffect(note_id=str(note_id)).apply()]
    ```
* * *
##  Common use cases 
  - **Concurrent edit protection** — prevent multiple providers from editing the same note simultaneously; the second user sees a banner and disabled inputs until the first provider's session expires.
  - **Role-based note type access** — restrict certain note types (e.g. sensitive clinical notes) to a specific set of staff members.
  - **Sensitive note hiding** — blur the content of notes containing sensitive information for users who should not see the full details.
For full working implementations of these patterns, see the [**note-timeline-restrictions**](https://github.com/canvas-medical/canvas-plugins/tree/main/example-plugins/note-timeline-restrictions) example plugin, which covers concurrent edit locking, role-based access via a dashboard, automatic lock expiry via a cron job, and real-time updates.
----- END PAGE https://docs.canvasmedical.com/sdk/effect-note-restrictions/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-notes/
The Canvas SDK provides effects to facilitate creating, updating, and managing **visit notes** , **appointments** , and **schedule events**. Below you'll find detailed documentation for each effect type and their operations.
##  Note Effect 
The `Note` effect facilitates the creation and updating of visit notes for patients.
###  Create Note 
Creates a new note. Can be passed an optional UUID as `instance_id` from the `uuid.uuid4` library, or will be assigned one if not present. Passing a user-set UUID as the `instance_id` allows for [assigning commands to the note](/sdk/commands/#chaining-methods-with-a-user-set-uuid) in the same plugin action.
####  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`instance_id` | `UUID` or `str` | Identifier for the note | No  
`note_type_id` | `UUID` or `str` | Identifier for the note type | Yes  
`datetime_of_service` | `datetime.datetime` | When the service was provided | Yes  
`patient_id` | `str` | Identifier for the patient | Yes  
`practice_location_id` | `UUID` or `str` | Identifier for the practice location | Yes  
`provider_id` | `str` | Identifier for the provider | Yes  
`title` | `str` or `None` | Optional title for the note | No  
`supervising_provider_id` | `str` or `None` | Staff identifier for the supervising provider | No  
####  Implementation Details 
  - Validates that the note type exists and has an appropriate category
  - Ensures the patient exists in the system
  - Verifies that the practice location and provider are valid
  - If `supervising_provider_id` is provided, validates that the Staff record exists
####  Example Usage 
    ```python
    import datetime
    from canvas_sdk.effects.note.note import Note
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        def compute(self):
            note_effect = Note(
                note_type_id="note-type-uuid",
                datetime_of_service=datetime.datetime.now(),
                patient_id="patient-uuid",
                practice_location_id="practice-location-uuid",
                provider_id="provider-uuid"
            )
            return [note_effect.create()]
    ```
###  Update Note 
Updates an existing note. Only certain fields can be modified after creation.
####  Attributes 
Attribute | Type | Description | Required | Updatable  
---|---|---|---|---  
`instance_id` | `UUID` or `str` | Identifier of the note to update | Yes | No  
`title` | `str` or `None` | Updated title for the note | No | Yes  
`datetime_of_service` | `datetime.datetime` | Updated service date/time | No | Yes  
`practice_location_id` | `UUID` or `str` | Updated practice location | No | Yes  
`provider_id` | `str` | Updated provider | No | Yes  
`supervising_provider_id` | `str` or `None` | Staff identifier for the supervising provider | No | Yes  
**Note** : `patient_id` and `note_type_id` cannot be updated after creation.
####  Example Usage 
    ```python
    import datetime
    from canvas_sdk.effects.note.note import Note
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        def compute(self):
            note_effect = Note(instance_id="existing-note-uuid")
            note_effect.title = "Updated Consultation Notes"
            note_effect.datetime_of_service = datetime.datetime.now()
            return [note_effect.update()]
    ```
###  Fax Note 
Sends an existing note via fax to a specified recipient. This effect allows you to transmit patient notes to external healthcare providers or facilities.
####  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`note_id` | `UUID` or `str` | Identifier of the note to fax | Yes  
`recipient_name` | `str` | Name of the fax recipient | Yes  
`recipient_fax_number` | `str` | Fax number of the recipient. Should include the country code | Yes  
`include_coversheet` | `bool` | Whether to include a coversheet with the fax | No  
`subject` | `str` or `None` | Subject line for the coversheet (required if coversheet used) | No  
`comment` | `str` or `None` | Additional comments for coversheet (required if coversheet used) | No  
`location_id` | `UUID` or `str` or `None` | Practice location ID (required if coversheet used) | No  
####  Implementation Details 
  - Validates that the note exists in the system
  - If `include_coversheet` is `True`, the following fields become required: 
    - `subject`: The subject line for the coversheet
    - `comment`: Additional comments to include on the coversheet
    - `location_id`: The practice location identifier (must exist in the system)
  - Validates that the practice location exists if provided
####  Example Usage 
    ```python
    from canvas_sdk.effects.fax.note import FaxNoteEffect
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        def compute(self):
            # Basic fax without coversheet
            fax_effect = FaxNoteEffect(
                note_id="existing-note-uuid",
                recipient_name="Dr. Jane Smith",
                recipient_fax_number="15551234567"
            )
            return [fax_effect.apply()]
    ```
####  Example with Coversheet 
    ```python
    from canvas_sdk.effects.fax.note import FaxNoteEffect
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        def compute(self):
            # Fax with coversheet
            fax_effect = FaxNoteEffect(
                note_id="existing-note-uuid",
                recipient_name="Dr. Jane Smith",
                recipient_fax_number="15551234567",
                include_coversheet=True,
                subject="Patient Referral - Follow-up Care",
                comment="Please review attached consultation notes for continuing care.",
                location_id="practice-location-uuid"
            )
            return [fax_effect.apply()]
    ```
###  Push Charges 
Pushes the charges from the Note to its associated Claim in the Revenue module. Has the exact same effect as clicking on the `Push charges` button in the Note footer.
####  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`instance_id` | `UUID` or `str` | Identifier of the note to update | Yes  
**Note** : `instance_id` must be a valid, existing Note, and its NoteTypeVersion must have `is_billable` = True.
####  Example Usage 
    ```python
    import datetime
    from canvas_sdk.effects.note.note import Note
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        def compute(self):
            note_effect = Note(instance_id="existing-note-uuid")
            return [note_effect.push_charges()]
    ```
> **Info:** This effect will be originated by the current actor that triggered the event, with a fallback to Canvas Bot if no actor is found. 
###  Lock 
Locks an existing note, preventing further modifications. Has the exact same effect as clicking on the `Lock` button in the Note footer.
####  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`instance_id` | `UUID` or `str` | Identifier of the note to lock | Yes  
**Note** : `instance_id` must be a valid, existing Note that is not already locked.
####  Example Usage 
    ```python
    from canvas_sdk.effects.note.note import Note
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        def compute(self):
            note_effect = Note(instance_id="existing-note-uuid")
            return [note_effect.lock()]
    ```
> **Info:** This effect will be originated by the current actor that triggered the event, with a fallback to Canvas Bot if no actor is found. 
###  Sign 
Signs an existing note, marking it as reviewed and approved by the provider. Has the exact same effect as clicking on the `Sign` button in the Note footer.
####  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`instance_id` | `UUID` or `str` | Identifier of the note to sign | Yes  
**Note** : `instance_id` must be a valid, existing Note that is not already signed.
####  Example Usage 
    ```python
    from canvas_sdk.effects.note.note import Note
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        def compute(self):
            note_effect = Note(instance_id="existing-note-uuid")
            return [note_effect.sign()]
    ```
> **Info:** This effect will be originated by the current actor that triggered the event, with a fallback to Canvas Bot if no actor is found. 
###  Unlock 
Unlocks a previously locked/signed note, allowing modifications again. Has the exact same effect as clicking on the `Unlock/Amend` button in the Note footer.
####  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`instance_id` | `UUID` or `str` | Identifier of the note to unlock | Yes  
**Note** : `instance_id` must be a valid, existing Note that is currently locked.
####  Example Usage 
    ```python
    from canvas_sdk.effects.note.note import Note
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        def compute(self):
            note_effect = Note(instance_id="existing-note-uuid")
            return [note_effect.unlock()]
    ```
> **Info:** This effect will be originated by the current actor that triggered the event, with a fallback to Canvas Bot if no actor is found. 
###  Check In 
Marks a patient as checked in for their appointment. Has the exact same effect as clicking on the `Check In` button in the Appointment note.
####  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`instance_id` | `UUID` or `str` | Identifier of the note for check-in | Yes  
**Note** : `instance_id` must be a valid, existing Note associated with an appointment.
####  Example Usage 
    ```python
    from canvas_sdk.effects.note.note import Note
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        def compute(self):
            note_effect = Note(instance_id="existing-note-uuid")
            return [note_effect.check_in()]
    ```
> **Info:** This effect will be originated by the current actor that triggered the event, with a fallback to Canvas Bot if no actor is found. 
###  No Show 
Marks an appointment as a no-show when the patient does not arrive. Has the exact same effect as marking an appointment as `No Show` in the Appointment note.
####  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`instance_id` | `UUID` or `str` | Identifier of the note to mark no-show | Yes  
**Note** : `instance_id` must be a valid, existing Note associated with an appointment.
####  Example Usage 
    ```python
    from canvas_sdk.effects.note.note import Note
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        def compute(self):
            note_effect = Note(instance_id="existing-note-uuid")
            return [note_effect.no_show()]
    ```
> **Info:** This effect will be originated by the current actor that triggered the event, with a fallback to Canvas Bot if no actor is found. 
###  Delete 
Deletes an existing note. Has the exact same effect as clicking on the `Delete` button in the Note footer.
####  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`instance_id` | `UUID` or `str` | Identifier of the note to delete | Yes  
**Note** : `instance_id` must be a valid, existing Note whose current state allows deletion (e.g. `NEW`, `CONVERTED`, `UNLOCKED`, `PUSHED`, or `UNDELETED`).
####  Example Usage 
    ```python
    from canvas_sdk.effects.note.note import Note
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        def compute(self):
            note_effect = Note(instance_id="existing-note-uuid")
            return [note_effect.delete()]
    ```
> **Info:** This effect will be originated by the current actor that triggered the event, with a fallback to Canvas Bot if no actor is found. 
###  Undelete 
Restores a previously deleted note. Has the exact same effect as clicking on the `Restore` button on a deleted note.
####  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`instance_id` | `UUID` or `str` | Identifier of the note to restore | Yes  
**Note** : `instance_id` must be a valid, existing Note that is currently in the `DELETED` state.
####  Example Usage 
    ```python
    from canvas_sdk.effects.note.note import Note
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        def compute(self):
            note_effect = Note(instance_id="existing-note-uuid")
            return [note_effect.undelete()]
    ```
> **Info:** This effect will be originated by the current actor that triggered the event, with a fallback to Canvas Bot if no actor is found. 
###  Discharge 
Locks and discharges an inpatient note. Has the exact same effect as clicking on the `Lock and discharge` button in the Inpatient note footer.
####  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`instance_id` | `UUID` or `str` | Identifier of the inpatient note to discharge | Yes  
**Note** : `instance_id` must be a valid, existing Note whose `NoteTypeVersion.category` is `INPATIENT`, and whose current state allows discharge (`NEW`, `CONVERTED`, `UNLOCKED`, or `UNDELETED`).
####  Example Usage 
    ```python
    from canvas_sdk.effects.note.note import Note
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        def compute(self):
            note_effect = Note(instance_id="existing-inpatient-note-uuid")
            return [note_effect.discharge()]
    ```
> **Info:** This effect will be originated by the current actor that triggered the event, with a fallback to Canvas Bot if no actor is found. 
###  Upsert Metadata 
Creates or updates a metadata entry for the specified note. For detailed documentation on note metadata management, see [NoteMetadata Effect](/sdk/effect-note-metadata/).
####  Parameters 
Parameter | Type | Description | Required  
---|---|---|---  
`instance_id` | `UUID` or `str` | Identifier of the note (set on the `Note` effect) | Yes  
`key` | `str` | Unique identifier for the metadata entry within the note context | Yes  
`value` | `str` | The metadata value to store | Yes  
####  Example Usage 
    ```python
    from canvas_sdk.effects.note.note import Note
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        def compute(self):
            note = Note(instance_id="existing-note-uuid")
            return [note.upsert_metadata(key="my_plugin:custom_key", value="custom_value")]
    ```
##  ScheduleEvent Effect 
The `ScheduleEvent` effect enables creating, updating, and deleting schedule events for providers, with optional patient association.
###  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`note_type_id` | `UUID` or `str` | Identifier for the note type (must be of category `SCHEDULE_EVENT`) | Yes  
`patient_id` | `str` or `None` | Identifier for the patient (if applicable) | Conditional  
`description` | `str` or `None` | Custom description for the event | Conditional  
`start_time` | `datetime.datetime` | Start time of the event | Yes  
`duration_minutes` | `int` | Duration of the event in minutes | Yes  
`practice_location_id` | `UUID` or `str` | Identifier for the practice location | Yes  
`provider_id` | `str` | Identifier for the provider | Yes  
`status` | `AppointmentProgressStatus` or `None` | Status of the event | No  
`external_identifiers` | `list[AppointmentIdentifier]` or `None` | External system identifiers | No  
###  Implementation Details 
  - Validates that the note type exists and is of category `SCHEDULE_EVENT`
  - Ensures patient is provided if the note type requires it
  - Verifies that custom descriptions are only used for note types that allow them
  - Validates that the practice location and provider exist
###  Example Usage 
    ```python
    import datetime
    from canvas_sdk.effects.note.appointment import ScheduleEvent
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        def compute(self):
            schedule_event_effect = ScheduleEvent(
                note_type_id="schedule-event-note-type-uuid",
                patient_id="patient-uuid",  # Optional depending on note type
                description="Team meeting",  # Optional depending on note type
                start_time=datetime.datetime.now(),
                duration_minutes=30,
                practice_location_id="practice-location-uuid",
                provider_id="provider-uuid"
            )
            return [schedule_event_effect.create()]
    ```
###  Update Schedule Event 
Updates an existing schedule event in place.
####  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`instance_id` | `UUID` or `str` | Identifier of the event to update | Yes  
`start_time` | `datetime.datetime` | New start time | No  
`duration_minutes` | `int` | New duration in minutes | No  
`description` | `str` or `None` | Updated description | No  
`practice_location_id` | `UUID` or `str` | New practice location | No  
`provider_id` | `str` | New provider | No  
`status` | `AppointmentProgressStatus` or `None` | Updated status | No  
####  Example Usage 
    ```python
    import datetime
    from canvas_sdk.effects.note import AppointmentIdentifier
    from canvas_sdk.effects.note.appointment import ScheduleEvent
    from canvas_sdk.effects.note.base import AppointmentIdentifier
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        def compute(self):
            schedule_event_effect = ScheduleEvent(instance_id="existing-event-uuid")
            schedule_event_effect.start_time = datetime.datetime.now() + datetime.timedelta(days=1)
            schedule_event_effect.duration_minutes = 60
            schedule_event_effect.description = "Rescheduled team meeting"
            schedule_event_effect.external_identifiers = [
                AppointmentIdentifier(system="test_system", value="123TEST")
            ]
            return [schedule_event_effect.update()]
    ```
###  Reschedule Schedule Event 
Reschedules an existing schedule event by creating a new event and cancelling the original. This maintains the event history and ensures proper tracking of rescheduled events.
####  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`instance_id` | `UUID` or `str` | Identifier of the event to reschedule | Yes  
`start_time` | `datetime.datetime` | New start time | No  
`duration_minutes` | `int` | New duration in minutes | No  
`description` | `str` or `None` | Updated description | No  
`practice_location_id` | `UUID` or `str` | New practice location | No  
`provider_id` | `str` | New provider | No  
`status` | `AppointmentProgressStatus` or `None` | Updated status | No  
`external_identifiers` | `list[AppointmentIdentifier]` or `None` | Updated external identifiers | No  
**Note** : At least one field (besides `instance_id`) must be modified.
####  Example Usage 
    ```python
    import datetime
    from canvas_sdk.effects.note.appointment import ScheduleEvent
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        def compute(self):
            schedule_event_effect = ScheduleEvent(instance_id="existing-event-uuid")
            schedule_event_effect.start_time = datetime.datetime.now() + datetime.timedelta(hours=3)
            schedule_event_effect.duration_minutes = 45
            return [schedule_event_effect.reschedule()]
    ```
###  Delete Schedule Event 
Marks a schedule event as cancelled.
####  Example Usage 
    ```python
    import datetime
    from canvas_sdk.effects.note.appointment import ScheduleEvent
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        def compute(self):
            schedule_event_effect = ScheduleEvent(instance_id="existing-event-uuid")
            return [schedule_event_effect.delete()]
    ```
* * *
##  Appointment Effect 
The `Appointment` effect facilitates creating, updating, and cancelling patient appointments with providers.
###  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`appointment_note_type_id` | `UUID` or `str` | Identifier for the appointment note type (must be of category `ENCOUNTER` and scheduleable) | Yes  
`patient_id` | `str` | Identifier for the patient | Yes  
`meeting_link` | `str` or `None` | Link for virtual appointments | No  
`start_time` | `datetime.datetime` | Start time of the appointment | Yes  
`duration_minutes` | `int` | Duration of the appointment in minutes | Yes  
`practice_location_id` | `UUID` or `str` | Identifier for the practice location | Yes  
`provider_id` | `str` | Identifier for the provider | Yes  
`status` | `AppointmentProgressStatus` or `None` | Status of the appointment | No  
`external_identifiers` | `list[AppointmentIdentifier]` or `None` | External system identifiers | No  
###  Implementation Details 
  - Validates that the appointment note type exists, is of category `ENCOUNTER`, and is scheduleable
  - Ensures the patient exists in the system
  - Verifies that the practice location and provider exist
###  Example Usage 
    ```python
    import datetime
    from canvas_sdk.effects.note.appointment import Appointment
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        def compute(self):
            appointment_effect = Appointment(
                appointment_note_type_id="appointment-note-type-uuid",
                patient_id="patient-uuid",
                meeting_link="https://zoom.us/example-link",  # Optional
                start_time=datetime.datetime.now(),
                duration_minutes=60,
                practice_location_id="practice-location-uuid",
                provider_id="provider-uuid"
            )
            return appointment_effect.create()
    ```
###  Update Appointment 
Updates an existing appointment in place.
####  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`instance_id` | `UUID` or `str` | Identifier of appointment to update | Yes  
`start_time` | `datetime.datetime` | New start time | No  
`duration_minutes` | `int` | New duration in minutes | No  
`meeting_link` | `str` or `None` | Updated meeting link | No  
`practice_location_id` | `UUID` or `str` | New practice location | No  
`provider_id` | `str` | New provider | No  
`status` | `AppointmentProgressStatus` or `None` | Updated status | No  
`external_identifiers` | `list[AppointmentIdentifier]` or `None` | Updated external identifiers | No  
**Note** : `patient_id` cannot be updated after creation.
####  Example Usage 
    ```python
    import datetime
    from canvas_sdk.effects.note.appointment import Appointment
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        def compute(self):
            appointment_effect = Appointment(instance_id="existing-appointment-uuid")
            appointment_effect.start_time = datetime.datetime.now() + datetime.timedelta(hours=2)
            appointment_effect.duration_minutes = 45
            appointment_effect.meeting_link = "https://new-meeting-link.com"
            return appointment_effect.update()
    ```
###  Reschedule Appointment 
Reschedules an existing appointment by creating a new appointment and cancelling the original. This maintains the appointment history and ensures proper tracking of rescheduled appointments.
####  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`instance_id` | `UUID` or `str` | Identifier of appointment to reschedule | Yes  
`start_time` | `datetime.datetime` | New start time | No  
`duration_minutes` | `int` | New duration in minutes | No  
`meeting_link` | `str` or `None` | Updated meeting link | No  
`practice_location_id` | `UUID` or `str` | New practice location | No  
`provider_id` | `str` | New provider | No  
`status` | `AppointmentProgressStatus` or `None` | Updated status | No  
`external_identifiers` | `list[AppointmentIdentifier]` or `None` | Updated external identifiers | No  
**Note** : At least one field (besides `instance_id`) must be modified. `patient_id` cannot be updated after creation.
####  Example Usage 
    ```python
    import datetime
    from canvas_sdk.effects.note.appointment import Appointment
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        def compute(self):
            appointment_effect = Appointment(instance_id="existing-appointment-uuid")
            appointment_effect.start_time = datetime.datetime.now() + datetime.timedelta(days=1)
            appointment_effect.duration_minutes = 60
            return appointment_effect.reschedule()
    ```
###  Cancel Appointment 
Cancels an existing appointment and updates its status.
####  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`instance_id` | `UUID` or `str` | Identifier of the appointment to cancel | Yes  
**Note** : `instance_id` must be a valid, existing Appointment whose current state allows cancellation. An appointment can only be cancelled when it is in the `BOOKED` or `REVERTED` state.
####  Example Usage 
    ```python
    from canvas_sdk.effects.note.appointment import Appointment
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        def compute(self):
            appointment_effect = Appointment(instance_id="existing-appointment-uuid")
            return appointment_effect.cancel()
    ```
###  Revert Appointment 
Reverts a booked or checked-in appointment back to a state where it can be checked in, cancelled, rescheduled, or marked as no-show.
####  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`instance_id` | `UUID` or `str` | Identifier of the appointment to revert | Yes  
**Note** : `instance_id` must be a valid, existing Appointment whose current state allows reversion. An appointment can only be reverted when it is in the `CANCELLED`, `CONVERTED`, or `NOSHOW` state.
####  Example Usage 
    ```python
    from canvas_sdk.effects.note.appointment import Appointment
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        def compute(self):
            appointment_effect = Appointment(instance_id="existing-appointment-uuid")
            return appointment_effect.revert()
    ```
##  Managing Appointment Labels 
Canvas supports adding up to 3 labels per appointment for categorization and workflow automation. Labels can be managed programmatically using the appointment label effects.
For detailed documentation on appointment label management, see [Appointment Label Effects](/sdk/effect-appointment-labels/).
###  Quick Example 
    ```python
    from canvas_sdk.effects.note.appointment import AddAppointmentLabel, RemoveAppointmentLabel
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        RESPONDS_TO = [EventType.Name(EventType.APPOINTMENT_LABEL_ADDED), EventType.Name(EventType.APPOINTMENT_LABEL_REMOVED)]
        def compute(self):
            # Add labels to an appointment
            add_effect = AddAppointmentLabel(
                appointment_id="appointment-uuid",
                labels={"URGENT", "FOLLOW_UP"}
            )
            # Remove labels from an appointment
            remove_effect = RemoveAppointmentLabel(
                appointment_id="appointment-uuid",
                labels={"CANCELLED"}
            )
            return [add_effect.apply(), remove_effect.apply()]
    ```
* * *
##  Validation 
All effects perform comprehensive validation before execution:
  1. **Entity Existence** : Validates that referenced entities (patients, providers, practice locations, note types) exist in the system
  2. **Type Compatibility** : Ensures note types are appropriate for the intended operation: 
     - Visit notes cannot use `APPOINTMENT`, `SCHEDULE_EVENT`, `MESSAGE`, or `LETTER` note types
     - Schedule events must use `SCHEDULE_EVENT` note types
     - Appointments must use `ENCOUNTER` note types that are scheduleable
  3. **Field Requirement Enforcement** : The system validates conditional field requirements based on note type configurations: 
     - **Patient Association Requirements** : For note types with `is_patient_required=True`, the system enforces that a valid patient ID is provided. This is particularly important for schedule events that may or may not be associated with specific patients.
     - **Custom Description Validation** : When a note type has `allow_custom_title=False`, the system prevents custom descriptions from being added. This ensures adherence to standardized naming conventions for certain types of appointments and events.
     - **Required Field Validation** : All required fields are checked for proper values and formats before the effect is executed.
  4. **Update Restrictions** : Certain fields cannot be modified after creation: 
     - **Notes** : `patient_id` and `note_type_id` are immutable
     - **Appointments** : `patient_id` is immutable
     - **All Effects** : At least one field must be modified for an update operation to succeed
----- END PAGE https://docs.canvasmedical.com/sdk/effect-notes/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-observation/
The `Observation` effect provides a unified way to create and update clinical observations within the Canvas platform. Observations can include vitals (blood pressure, temperature, etc.), lab results, and other clinical measurements. The effect supports structured coding using standard terminologies (LOINC, SNOMED), components for multi-part measurements, and value codings for interpretation.
##  Attributes 
Name | Type | Description  
---|---|---  
`observation_id` | `str` or `UUID` or `None` | Unique identifier of an existing observation. Must be unset when creating; required when updating.  
`patient_id` | `str` or `None` | ID of the patient for this observation. Required when creating.  
`is_member_of_id` | `str` or `UUID` or `None` | Reference to a parent observation (for grouping related observations).  
`category` | `str` or `list[str]` or `None` | Category of observation (e.g., "vital-signs", "laboratory", "imaging"). Can be a single category or a list of categories.  
`units` | `str` or `None` | Unit of measure for the observation value (e.g., "mmHg", "mg/dL").  
`value` | `str` or `None` | The observation value as a string.  
`note_id` | `int` or `None` | ID of the note associated with this observation.  
`name` | `str` or `None` | Human-readable name for the observation. Required when creating.  
`effective_datetime` | `datetime` or `None` | Date and time when the observation was taken. Required when creating.  
`codings` | `list[CodingData]` or `None` | List of standardized codes identifying this observation (e.g., LOINC codes).  
`components` | `list[ObservationComponentData]` or `None` | List of components for multi-part observations (e.g., systolic and diastolic BP).  
`value_codings` | `list[CodingData]` or `None` | List of coded values for interpretation (e.g., "normal", "abnormal").  
##  Helper Classes 
###  `CodingData`
Represents a standardized code from a terminology system (LOINC, SNOMED, etc.).
Name | Type | Description  
---|---|---  
`code` | `str` | The code value from the terminology system.  
`display` | `str` | Human-readable display text for the code.  
`system` | `str` | URI identifying the terminology system (e.g., "http://loinc.org").  
`version` | `str` | Version of the terminology system. Defaults to empty string.  
`user_selected` | `bool` | Whether this code was explicitly selected by the user. Defaults to False.  
###  `ObservationComponentData`
Represents a component of a multi-part observation (e.g., systolic and diastolic blood pressure).
Name | Type | Description  
---|---|---  
`value_quantity` | `str` | The numeric value of this component.  
`value_quantity_unit` | `str` | Unit of measure for this component value.  
`name` | `str` | Name of this component.  
`codings` | `list[CodingData]` or `None` | Standardized codes identifying this component.  
##  Methods 
The examples below share this setup:
    ```python
    import datetime
    from canvas_sdk.effects.observation import Observation, CodingData, ObservationComponentData
    from canvas_sdk.v1.data.observation import Observation as ObservationModel
    from canvas_sdk.v1.data.patient import Patient
    patient = Patient.objects.first()
    ```
###  create() → Effect 
Create a new observation record.
  - **Effect Type:** `CREATE_OBSERVATION`
  - **Payload:** `{ "data": { patient_id, name, effective_datetime, ... } }`
####  Validation 
  - `observation_id` must **not** be set (will be generated by the system)
  - `patient_id` is **required**
  - `name` is **required**
  - `effective_datetime` is **required**
  - If `is_member_of_id` is provided, the parent observation must exist
####  Example: Blood Pressure Observation 
    ```python
    # Create a blood pressure observation with components and codings
    bp_observation = Observation(
        patient_id=patient.id,
        name="Blood Pressure",
        category="vital-signs",
        value="120/80",
        units="mmHg",
        effective_datetime=datetime.datetime.now(),
        codings=[
            CodingData(
                code="85354-9",
                display="Blood pressure panel with all children optional",
                system="http://loinc.org",
                version="2.73",
                user_selected=True,
            )
        ],
        components=[
            ObservationComponentData(
                value_quantity="120",
                value_quantity_unit="mmHg",
                name="Systolic Blood Pressure",
                codings=[
                    CodingData(
                        code="8480-6",
                        display="Systolic blood pressure",
                        system="http://loinc.org",
                    )
                ],
            ),
            ObservationComponentData(
                value_quantity="80",
                value_quantity_unit="mmHg",
                name="Diastolic Blood Pressure",
                codings=[
                    CodingData(
                        code="8462-4",
                        display="Diastolic blood pressure",
                        system="http://loinc.org",
                    )
                ],
            ),
        ],
        value_codings=[
            CodingData(
                code="normal",
                display="Normal",
                system="http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation",
            )
        ],
    )
    effect_create = bp_observation.create()
    ```
####  Example: Simple Lab Result 
    ```python
    # Create a simple lab observation
    glucose = Observation(
        patient_id=patient.id,
        name="Glucose",
        category="laboratory",
        value="95",
        units="mg/dL",
        effective_datetime=datetime.datetime.now(),
        codings=[
            CodingData(
                code="2339-0",
                display="Glucose [Mass/volume] in Blood",
                system="http://loinc.org",
            )
        ],
    )
    effect_create_lab = glucose.create()
    ```
####  Example: Multiple Categories 
    ```python
    # Create an observation that belongs to multiple categories
    comprehensive_assessment = Observation(
        patient_id=patient.id,
        name="Comprehensive Physical Assessment",
        category=["vital-signs", "exam"],  # Multiple categories
        value="Normal",
        effective_datetime=datetime.datetime.now(),
        codings=[
            CodingData(
                code="29545-1",
                display="Physical examination",
                system="http://loinc.org",
            )
        ],
    )
    effect_create_multi = comprehensive_assessment.create()
    ```
###  update() → Effect 
Update an existing observation.
  - **Effect Type:** `UPDATE_OBSERVATION`
  - **Payload:** `{ "data": { observation_id, <dirty_fields> } }`
  - Only fields marked dirty (modified on the model) are included in the update.
####  Validation 
  - `observation_id` is **required** and must reference an existing observation
  - All other fields are optional; only dirty (modified) fields are updated
  - If `is_member_of_id` is provided, the parent observation must exist
####  Example 
    ```python
    # Find an existing observation
    existing_obs = ObservationModel.objects.filter(patient_id=patient.id).first()
    # Update the blood pressure values
    updated_bp = Observation(
        observation_id=existing_obs.id,
        value="130/85",
        units="mmHg",
        components=[
            ObservationComponentData(
                value_quantity="130",
                value_quantity_unit="mmHg",
                name="Systolic Blood Pressure",
                codings=[
                    CodingData(
                        code="8480-6",
                        display="Systolic blood pressure",
                        system="http://loinc.org",
                    )
                ],
            ),
            ObservationComponentData(
                value_quantity="85",
                value_quantity_unit="mmHg",
                name="Diastolic Blood Pressure",
                codings=[
                    CodingData(
                        code="8462-4",
                        display="Diastolic blood pressure",
                        system="http://loinc.org",
                    )
                ],
            ),
        ],
    )
    effect_update = updated_bp.update()
    ```
###  enter_in_error() → Effect 
Marks an existing observation as entered in error. Use this when an observation was recorded incorrectly and should be flagged rather than deleted.
  - **Effect Type:** `ENTER_IN_ERROR_OBSERVATION`
  - **Payload:** `{ "data": { observation_id } }`
  - Only `observation_id` is allowed; setting any other field will raise a validation error.
####  Validation 
  - `observation_id` is **required** and must reference an existing observation
  - All other fields must **not** be set (only `observation_id` is allowed)
  - The observation must not already be entered in error
  - The observation must not belong to a locked note
####  Example 
    ```python
    # Find an observation that was recorded incorrectly
    erroneous_obs = ObservationModel.objects.filter(patient_id=patient.id).first()
    # Mark it as entered in error
    error_observation = Observation(observation_id=erroneous_obs.id)
    effect_error = error_observation.enter_in_error()
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/effect-observation/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-patient-facility-address/
The `PatientFacilityAddress` effect enables the creation, updating, and deletion of patient facility address records within Canvas. Patient facility addresses link patients to healthcare facilities, with optional room number information. The address details are automatically populated from the linked facility.
You can either reference an existing facility by ID, or create a new facility inline by providing the facility details.
##  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`id` | `str` or `UUID` | ID of the patient facility address (for update/delete) | Yes (update/delete)  
`patient_id` | `str` or `UUID` | ID of the patient | Yes (create)  
`facility_id` | `str` or `UUID` | ID of an existing facility to link | Yes (if not creating new)  
`facility_name` | `str` | Name of new facility to create | Yes (if creating new facility)  
`facility_npi_number` | `str` | NPI number for new facility | No  
`facility_phone_number` | `str` | Phone number for new facility | No  
`facility_fax_number` | `str` | Fax number for new facility | No  
`facility_active` | `bool` | Whether the new facility is active | No  
`facility_line1` | `str` | Street address line 1 for new facility | No  
`facility_line2` | `str` | Street address line 2 for new facility | No  
`facility_city` | `str` | City for new facility | Yes (if creating new facility)  
`facility_district` | `str` | District for new facility | No  
`facility_state_code` | `str` | State code for new facility (e.g., "CA", "NY") | Yes (if creating new facility)  
`facility_postal_code` | `str` | Postal code for new facility | Yes (if creating new facility)  
`room_number` | `str` | Room number at the facility | No  
`address_type` | `AddressType` or `str` | Type of address: "physical" or "both" | No (defaults to "physical")  
##  Facility Reference Options 
When creating a patient facility address, you must either:
  1. **Reference an existing facility** by providing `facility_id`
  2. **Create a new facility inline** by providing facility creation fields (`facility_name`, `facility_city`, `facility_state_code`, `facility_postal_code`)
> **Warning:** You cannot specify both `facility_id` and facility creation fields. Use one approach or the other. 
###  Required Fields for Inline Facility Creation 
When creating a new facility inline, the following fields are required:
  - `facility_name`
  - `facility_city`
  - `facility_state_code`
  - `facility_postal_code`
##  Address Type 
The `address_type` field accepts the following values:
Value | Description  
---|---  
`physical` | Physical/street address (default)  
`both` | Both physical and mailing address  
##  Effect Methods 
###  `.create()`
Creates a new patient facility address. Requires `patient_id` and either `facility_id` or facility creation fields.
**Effect Type:** `CREATE_PATIENT_FACILITY_ADDRESS`
###  `.update()`
Updates an existing patient facility address. Requires `id` of the address to update.
**Effect Type:** `UPDATE_PATIENT_FACILITY_ADDRESS`
###  `.delete()`
Deletes an existing patient facility address. Requires `id` of the address to delete.
**Effect Type:** `DELETE_PATIENT_FACILITY_ADDRESS`
##  Validation 
The effect validates:
  - **Create** : `patient_id` is required and must reference an existing patient
  - **Create** : Either `facility_id` or facility creation fields must be provided (not both)
  - **Create** : If `facility_id` is provided, it must reference an existing facility
  - **Create** : If creating a new facility, all required facility fields must be provided
  - **Update/Delete** : `id` is required and must reference an existing patient facility address
  - **Update** : If updating facility, same rules apply as create (facility_id OR creation fields)
  - `address_type` must be "physical" or "both" if provided
##  Example Usage 
###  Creating with Existing Facility 
    ```python
    from canvas_sdk.effects.patient_facility_address import PatientFacilityAddress, AddressType
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        def compute(self):
            effect = PatientFacilityAddress(
                patient_id="patient-uuid-here",
                facility_id="facility-uuid-here",
                room_number="101A",
                address_type=AddressType.PHYSICAL,
            )
            return [effect.create()]
    ```
###  Creating with New Facility 
    ```python
    from canvas_sdk.effects.patient_facility_address import PatientFacilityAddress, AddressType
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        def compute(self):
            effect = PatientFacilityAddress(
                patient_id="patient-uuid-here",
                facility_name="Downtown Medical Center",
                facility_line1="123 Main Street",
                facility_line2="Suite 400",
                facility_city="Boston",
                facility_state_code="MA",
                facility_postal_code="02101",
                facility_phone_number="617-555-1234",
                facility_npi_number="1234567890",
                room_number="Room 205",
                address_type=AddressType.PHYSICAL,
            )
            return [effect.create()]
    ```
###  Updating an Existing Address 
    ```python
    from canvas_sdk.effects.patient_facility_address import PatientFacilityAddress
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        def compute(self):
            # Update to use a different existing facility
            effect = PatientFacilityAddress(
                id="existing-address-uuid",
                facility_id="new-facility-uuid",
                room_number="202B",
            )
            return [effect.update()]
    ```
###  Deleting an Address 
    ```python
    from canvas_sdk.effects.patient_facility_address import PatientFacilityAddress
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        def compute(self):
            effect = PatientFacilityAddress(
                id="existing-address-uuid",
            )
            return [effect.delete()]
    ```
##  Notes 
  - The address details (line1, line2, city, state, country, postal_code) displayed for a patient facility address are automatically populated from the linked facility's address information.
  - When a facility's address is updated, all linked patient facility addresses are automatically updated to match. This synchronization happens asynchronously and applies to line1, line2, city, district, state_code, postal_code, and country fields. Non-address changes to the facility (such as name, NPI number, or phone number) do not trigger this cascade.
  - When creating a new facility inline, the facility is created first, then linked to the patient facility address.
  - Room number is optional and can be used to specify the patient's specific room within the facility.
----- END PAGE https://docs.canvasmedical.com/sdk/effect-patient-facility-address/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-patient-group/
The Canvas SDK provides effects for managing patient membership in groups. These effects are idempotent — adding a patient who is already a member or deactivating a patient who is not an active member will have no effect.
##  PatientGroupEffect 
An effect class for performing actions on a patient group. Instantiate it with a `group_id`, then call methods to add or deactivate members.
###  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`group_id` | `UUID` | The id of the [patient group](/sdk/data-patient-group/) | Yes  
###  Methods 
####  `add_member(patient_ids: list[str]) -> Effect`
Ensures one or more patients are members of the group.
Parameter | Type | Description  
---|---|---  
`patient_ids` | `list[str]` | List of [patient](/sdk/data-patient/) ids to add to the group  
####  `deactivate_member(patient_ids: list[str]) -> Effect`
Ensures one or more patients are not active members of the group. If a patient is currently locked in the group, this effect will be ignored for that patient.
Parameter | Type | Description  
---|---|---  
`patient_ids` | `list[str]` | List of [patient](/sdk/data-patient/) ids to deactivate from the group  
###  Example Usage 
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.patient_group import PatientGroupEffect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.v1.data import Patient, PatientGroup
    class AddMemberHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.PATIENT_UPDATED)
        def compute(self) -> list[Effect]:
            """Add patients to a group."""
            patient = Patient.objects.get(id=self.target)
            group = PatientGroup.objects.first()
            effect = PatientGroupEffect(group_id=str(group.id))
            return [effect.add_member(patient_ids=[str(patient.id)])]
    class DeactivateMemberHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.PATIENT_UPDATED)
        def compute(self) -> list[Effect]:
            """Deactivate a patient from a group."""
            patient = Patient.objects.get(id=self.target)
            group = PatientGroup.objects.first()
            effect = PatientGroupEffect(group_id=str(group.id))
            return [effect.deactivate_member(patient_ids=[str(patient.id)])]
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/effect-patient-group/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-patient-metadata/
The `PatientMetadata` effect provides a flexible key-value storage system for patient-specific data within the Canvas system. This effect enables the creation and updating of custom metadata entries associated with patient records, allowing for extensible patient information storage beyond standard demographic fields.
##  Overview 
Patient metadata serves as a powerful extension mechanism for storing custom patient-related information that doesn't fit within the standard patient data model. It uses the `.upsert(value)` method to apply a value to the key attributed with the Metadata effect object.
##  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`patient_id` | `str` | Id of the [Patient(/sdk/data-patient/)] record to associate metadata with | Yes  
`key` | `str` | Unique identifier for the metadata entry within the patient context | Yes  
##  Methods 
###  upsert(value: str) → Effect 
Creates or updates a metadata entry for the specified patient and key combination.
####  Parameters 
Parameter | Type | Description | Required  
---|---|---|---  
`value` | `str` | The metadata value to store | Yes  
####  Behavior 
  - If a metadata entry with the specified key already exists for the patient, it will be updated with the new value
  - If no entry exists, a new metadata entry will be created
##  Implementation Details 
###  Validation 
The effect performs comprehensive validation before execution:
  1. **Patient Existence Validation** : Verifies that the referenced patient exists in the system
  - Queries the patient database to confirm the `patient_id` corresponds to an existing patient record
  - Returns a descriptive error if the patient is not found
  1. **Field Validation** : Ensures all required fields are provided and properly formatted
  - Both `patient_id` and `key` must be non-empty strings
  - The `value` parameter in the `upsert` method must be provided
###  Data Structure 
The effect payload is structured as JSON with the following schema:
    ```json
    {
      "data": {
        "patient_id": "patient-id",
        "key": "metadata-key",
        "value": "metadata-value"
      }
    }
    ```
##  Example Usage 
###  Basic Usage 
    ```python
    from canvas_sdk.effects.patient_metadata import PatientMetadata
    # Create a metadata entry for patient preferences
    metadata = PatientMetadata(
        patient_id="550e8400e29b41d4a716446655440000",
        key="preferred_contact_time"
    )
    # Upsert the metadata value
    effect = metadata.upsert("morning")
    ```
###  Metadata Parsing Example 
    ```python
    import json
    import re
    from canvas_sdk.effects.patient_metadata import PatientMetadata
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.events import EventType
    class NarrativeMetadataExtractor(BaseHandler):
      """
      Extracts structured metadata from clinical narratives.
      """
      RESPONDS_TO = EventType.Name(EventType.PLAN_COMMAND__POST_UPDATE)
      def compute(self):
        patient_id = self.event.context["patient"]["id"]
        narrative = self.event.context.get("fields", {}).get("narrative", "")
        # Extract key-value pairs from narrative text
        # Pattern: key=somekey*value=somevalue
        key_match = re.search(r'key=([^*#_\s]+)', narrative)
        value_match = re.search(r'value=([^*#_\s]+)', narrative)
        if not (key_match and value_match):
          return []
        key = key_match.group(1)
        value = value_match.group(1)
        # Create metadata effect
        metadata = PatientMetadata(
          patient_id=patient_id,
          key=key
        )
        return [metadata.upsert(value)]
    ```
##  Best Practices 
###  Key Naming Conventions 
  1. **Use Descriptive Names** : Choose keys that clearly indicate the purpose of the metadata
  - Good: `external_mrn`, `preferred_pharmacy_id`, `risk_score_diabetes`
  - Avoid: `data1`, `temp`, `misc`
  1. **Namespace Your Keys** : When building integrations or modules, prefix keys to avoid collisions
  - Example: `integration_patient_id`, `module_diabetes_last_a1c_date`
###  Value Storage 
  1. **String Serialization** : All values are stored as strings. For complex data types: 
         ```python
         # Storing JSON data
         import json
         from canvas_sdk.effects.patient_metadata import PatientMetadata
         metadata = PatientMetadata(
             patient_id="550e8400e29b41d4a716446655440000",
             key="result"
         )
         complex_data = {"scores": [85, 92, 78], "average": 85.0}
         metadata.upsert(json.dumps(complex_data))
         ```
  2. **Boolean Values** : Store as "true" or "false" strings for consistency 
         ```python
         from canvas_sdk.effects.patient_metadata import PatientMetadata
         patient_consented = False
         metadata = PatientMetadata(
             patient_id="550e8400e29b41d4a716446655440000",
             key="boolean_value"
         )
         metadata.upsert("true" if patient_consented else "false")
         ```
##  Notes 
  - Metadata entries are patient-specific and isolated - the same key can have different values for different patients
  - There is no built-in versioning; updating a key overwrites the previous value
  - The system does not enforce any schema on metadata values - validation is the responsibility of the implementing code
----- END PAGE https://docs.canvasmedical.com/sdk/effect-patient-metadata/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-patient-timeline/
The Canvas SDK allows you to configure which note types a patient's chart shows and which the **New Note** button offers.
Both are controlled by the `PatientTimelineEffect` class, returned in response to the `PATIENT_TIMELINE__GET_CONFIGURATION` event, which fires when a patient's chart is loaded.
##  Excluding Note Types 
###  Attributes 
Attribute |  | Type | Description  
---|---|---|---  
`excluded_note_types` | optional | list[str] | A list of [`NoteType.unique_identifier`](/sdk/data-note/#notetype) values (UUIDs) to exclude from the patient's timeline. Defaults to `[]`.  
`allowed_new_note_types` | optional | list[str] | None | An allow-list of [`NoteType.unique_identifier`](/sdk/data-note/#notetype) values the **New Note** button may offer. `None` (the default) means no constraint; `[]` offers nothing, which hides the button. See Restricting note creation.  
The two attributes differ in scope, and you will usually want only one of them:
| `excluded_note_types` | `allowed_new_note_types`  
---|---|---  
direction | deny-list | allow-list  
existing notes on the timeline | **hidden** | visible  
timeline's note type filter | type removed | type still offered  
**New Note** button | type removed | restricted to the list  
direct permalink to such a note | permission error | unaffected  
several plugins respond | **unioned** | **unioned**  
###  Example Usage 
The `excluded_note_types` list must contain `unique_identifier` values from the `NoteType` model. Each `NoteType` has a `unique_identifier` (UUID) that you can look up by querying the model:
    ```python
    from canvas_sdk.v1.data.note import NoteType
    # Find the unique_identifier for a note type by name
    note_type = NoteType.objects.get(name="Office visit")
    note_type.unique_identifier  # e.g. UUID("a3b9c1d2-...")
    # Or list all note types with their unique_identifiers
    for nt in NoteType.objects.all():
        print(f"{nt.name}: {nt.unique_identifier}")
    ```
Then use those `unique_identifier` values in the effect:
    ```python
    from canvas_sdk.effects.patient.timeline import PatientTimelineEffect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers.base import BaseHandler
    from canvas_sdk.v1.data.note import NoteType
    class MyHandler(BaseHandler):
        RESPONDS_TO = [EventType.Name(EventType.PATIENT_TIMELINE__GET_CONFIGURATION)]
        def compute(self):
            # Use unique_identifier
            office_visit = NoteType.objects.get(name="Office visit", is_active=True)
            lab_visit = NoteType.objects.get(name="Lab visit", is_active=True)
            effect = PatientTimelineEffect(
                excluded_note_types=[
                    str(office_visit.unique_identifier),
                    str(lab_visit.unique_identifier),
                ]
            )
            return [effect.apply()]
    ```
###  Behavior 
> **Info:** **Chart Review notes cannot be excluded.** Even if a `CHART_REVIEW` note type is included in the `excluded_note_types` list, it will always be shown on the timeline. The system automatically removes it from any exclusion list. 
  - **Permalink access** : If a user tries to directly access a note whose type has been excluded, they will receive a permission error.
  - **Multiple plugins** : If multiple plugins respond to the `PATIENT_TIMELINE__GET_CONFIGURATION` event, the excluded note types from all responses are combined.
  - **Note creation** : An excluded note type is also removed from the patient chart's **New Note** button and from the timeline's note type filter, so users cannot pick that type when creating a note. This governs what the UI offers — it does not reject a note of an excluded type created directly through the API.
> **Info:** **To restrict note creation without hiding existing notes:** `excluded_note_types` hides a patient's existing notes of that type _and_ removes the type from the **New Note** button. If you only want to restrict what the button offers, while leaving the patient's history visible and filterable, use `allowed_new_note_types` instead. 
##  Restricting note creation 
`allowed_new_note_types` is an **allow-list** of the note types the **New Note** button may offer. It affects note _creation_ only: existing notes of a withheld type stay on the timeline, and the timeline's note type filter keeps offering that type, so a provider can still see and filter the history they are being stopped from adding to.
A common use is limiting which note types a given provider can originate. An organization might want only certain staff sending text messages to a patient, for example: the **New Note** button offers the Message type to those roles and withholds it from everyone else, while every provider can still read the messages already on the patient's chart and filter the timeline by them.
Inactive and deprecated note types are never offered, whether or not a plugin responds.
The example below allow-lists by the staff member's clinical role, so a nurse can send a message or log a phone call while only a physician is offered an office visit.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.patient.timeline import PatientTimelineEffect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.v1.data.note import NoteType
    from canvas_sdk.v1.data.staff import Staff
    ALLOWED_BY_ROLE = {
        "MD": ["Office visit", "Phone call", "Message"],
        "RN": ["Phone call", "Message"],
    }
    class RestrictNewNoteTypes(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.PATIENT_TIMELINE__GET_CONFIGURATION)
        def compute(self) -> list[Effect]:
            staff = Staff.objects.filter(user__dbid=self.event.actor.id).first()
            role = staff.top_role_abbreviation if staff else None
            allowed_names = ALLOWED_BY_ROLE.get(role or "", ["Message"])
            note_types = NoteType.objects.filter(is_active=True, name__in=allowed_names)
            return [
                PatientTimelineEffect(
                    allowed_new_note_types=[str(nt.unique_identifier) for nt in note_types]
                ).apply()
            ]
    ```
To hide the button entirely, return an empty allow-list:
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.patient.timeline import PatientTimelineEffect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class HideNewNoteButton(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.PATIENT_TIMELINE__GET_CONFIGURATION)
        def compute(self) -> list[Effect]:
            return [PatientTimelineEffect(allowed_new_note_types=[]).apply()]
    ```
###  Behavior 
What your plugin returns | Result  
---|---  
attribute omitted, or `None` | the full note type list, unchanged  
`allowed_new_note_types=[...]` | only those note types are offered  
`allowed_new_note_types=[]` | nothing is offered, so the **New Note** button is hidden entirely  
  - **Multiple plugins** : allow-lists from all responses are combined, the same way exclusions are. Note the consequence: a second plugin returning an allow-list _widens_ what a first one permits, so a restriction is only as tight as the most permissive plugin responding.
  - **Combined with exclusions** : a note type excluded via `excluded_note_types` stays out of the button even if the allow-list names it. Exclusions win because they affect far more — the timeline, the note type filter and permalink access — so they are the safer outcome when a plugin names the same type in both.
  - **Chart Review** : unlike exclusions, `CHART_REVIEW` is _not_ force-allowed here. Force-allowing it would make "nothing available" unreachable and the button could never be hidden.
  - **Plugin failures** : if the plugin runner cannot be reached, the note type list is left unconstrained rather than emptied.
> **Warning:** **This is a workflow guardrail, not an access control.** It governs what the **New Note** button offers. It does not reject a note of a restricted type created directly through the API. Do not rely on it to enforce access to sensitive note types — see [Note Restrictions](/sdk/effect-note-restrictions/) for controlling access to notes. 
> **Info:** **Note types are configured per instance.** The names above are illustrative, so check what exists on your instance before matching on `name` — a name that does not exist simply matches nothing, silently shortening your allow-list. A `unique_identifier` is generated per instance too, so it cannot be hard-coded in a plugin meant to run on more than one; look the note types up at runtime and keep the mapping configurable. An identifier that does not exist raises a `ValidationError` rather than failing quietly. 
###  Validation 
  - All provided UUIDs, in either attribute, must correspond to existing [NoteType](/sdk/data-note/#notetype) records in the system. If a note type UUID does not exist, a `ValidationError` will be raised with a message indicating which note type was not found.
  - Values that are not valid UUIDs will also raise a `ValidationError`.
----- END PAGE https://docs.canvasmedical.com/sdk/effect-patient-timeline/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-patient/
The `Patient` effect enables the creation and updating of patient records within the Canvas system. This effect captures demographic information, contact details, and clinical associations necessary for patient registration and updates.
##  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`first_name` | `str` | Patient's first name | Yes  
`last_name` | `str` | Patient's last name | Yes  
`middle_name` | `str` or `None` | Patient's middle name | No  
`birthdate` | `datetime.date` or `None` | Patient's date of birth | No  
`prefix` | `str` or `None` | Name prefix (e.g., "Dr.", "Mr.") | No  
`suffix` | `str` or `None` | Name suffix (e.g., "Jr.", "III") | No  
`sex_at_birth` | `PersonSex` or `None` | Patient's sex assigned at birth | No  
`nickname` | `str` or `None` | Patient's preferred name or nickname | No  
`social_security_number` | `str` or `None` | Patient's SSN | No  
`administrative_note` | `str` or `None` | Administrative notes about the patient | No  
`clinical_note` | `str` or `None` | Clinical notes about the patient | No  
`default_location_id` | `str` or `None` | The `id` of the [PracticeLocation](/sdk/data-practicelocation/#practicelocation) to set as the patient's default practice location | No  
`default_provider_id` | `str` or `None` | The `id` of the [Staff](/sdk/data-staff/#staff) member to set as the patient's default provider | No  
`active` | `bool` or `None` | Whether the patient record is active | No  
`deceased` | `bool` or `None` | Whether the patient is deceased | No  
`deceased_datetime` | `datetime.datetime` or `None` | Date and time of patient's death | No  
`deceased_cause` | `str` or `None` | Cause of patient's death | No  
`deceased_comment` | `str` or `None` | Additional comments about patient's death | No  
`biological_race_codes` | `list[str]` or `None` | CDC race codes describing the patient's biological race (e.g., `"2106-3"`) | No  
`cultural_ethnicity_codes` | `list[str]` or `None` | CDC ethnicity codes describing the patient's cultural ethnicity (e.g., `"2186-5"`) | No  
`previous_names` | `list[str]` or `None` | List of patient's previous names | No  
`contact_points` | list[PatientContactPoint] or `None` | Patient's contact information | No  
`contacts` | list[PatientContact] or `None` | The patient's contacts — emergency contacts, next-of-kin, and other related persons. See Managing patient contacts | No  
`external_identifiers` | list[PatientExternalIdentifier] or `None` | Patient's external identifiers | No  
`patient_id` | `str` or `None` | Patient id. Required for updates. Optional on creation, where it must be a 32-character hex string (a UUID4 without hyphens) — see Supplying a patient id on creation. | No  
`addresses` | list[PatientAddress] or `None` | Patient's addresses | No  
`preferred_pharmacies` | list[PatientPreferredPharmacy] or `None` | Patient's preferred pharmacies | No  
`metadata` | list[PatientMetadata] or `None` | Patient metadata | No  
##  PatientContactPoint 
The `PatientContactPoint` dataclass represents various methods of contacting the patient.
###  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`system` | `ContactPointSystem` | Type of contact (e.g., phone, email) | Yes  
`value` | `str` | The contact information value (e.g., phone number, email address) | Yes  
`use` | `ContactPointUse` | Purpose of the contact point (e.g., home, work) | Yes  
`rank` | `int` | Priority order of contact methods | Yes  
`has_consent` | `bool` or `None` | Whether consent has been given to use this contact method | No  
##  PatientContact 
The `PatientContact` dataclass represents one of the patient's contacts — an emergency contact, next-of-kin, or other related person.
A contact identifies its person in one of two ways, and you must supply one of them: either **inline** , by giving a `name` (with optional phone, email and comments), or by **reference** , by pointing `related_patient` at another Canvas patient. The reference form is what links two patients to each other, and Canvas displays such a contact from the referenced patient's own record rather than from the contact row.
###  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`name` | `str` or `None` | The contact's name, when the contact holds the person's details inline | One of `name` or `related_patient`  
`related_patient` | `str`, `uuid.UUID` or `None` | The patient id of an existing Canvas patient this contact refers to, used instead of `name` | One of `name` or `related_patient`  
`contact_identifier` | `str`, `uuid.UUID` or `None` | Identifies an existing contact. Omit it to add a contact; supply it to modify or remove one. See Managing patient contacts | No  
`phone_number` | `str` or `None` | The contact's phone number. Exactly 10 digits | No  
`email` | `str` or `None` | The contact's email address | No  
`comments` | `str` or `None` | Free-text notes about the contact | No  
`categories` | list[PatientContactCategory] or `None` | The contact's relationship categories | No  
`inactive` | `bool` or `None` | Set with `contact_identifier` to remove the contact | No  
##  PatientContactCategory 
The `PatientContactCategory` dataclass expresses a contact's relationship to the patient — emergency contact, next-of-kin, and so on — as a coding.
All three fields are required, and the coding must already exist in the instance. Look one up with the [ContactCategory](/sdk/data-patient/#contactcategory) data model rather than composing a coding by hand; a coding the instance does not have raises a validation error instead of being created.
###  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`code` | `str` | The category code (e.g., `"EMC"` for an emergency contact) | Yes  
`code_system` | `str` | The coding system the code belongs to (e.g., `"INTERNAL"`) | Yes  
`name` | `str` | The category's display name (e.g., `"Emergency contact"`) | Yes  
##  PatientExternalIdentifier 
The `PatientExternalIdentifier` dataclass represents an external identifier (ID) associated with the patient. An example would be the unique patient ID for a third party system integrated with Canvas EMR.
###  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`system` | `str` | URL of the system of origin for the external ID (e.g., `http://hl7.org/fhir/sid/us-ssn`) | Yes  
`value` | `str` | The external ID or membership number/value | Yes  
##  PatientAddress 
The `PatientAddress` dataclass represents a patient's address information.
###  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`line1` | `str` | Street address line 1 | Yes  
`line2` | `str` or `None` | Street address line 2 | No  
`city` | `str` | City name | Yes  
`state_code` | `str` | State code (e.g., "CA", "NY") | Yes  
`postal_code` | `str` | Postal/ZIP code | Yes  
`country` | `str` | Country code | Yes  
`use` | `AddressUse` | Address type (e.g., home, work) | Yes  
> **Warning:** Address updates are **replace-based**. When updating a patient's addresses, the provided address list will completely replace all existing addresses. If you provide an empty list, all existing addresses will be deleted. 
##  PatientPreferredPharmacy 
The `PatientPreferredPharmacy` dataclass represents a patient's preferred pharmacy, and if it's their default pharmacy.
Attribute | Type | Description | Required  
---|---|---|---  
`ncpdp_id` | `str` | The ncpdp ID of the pharmacy | Yes  
`default` | `bool` | True if it's the default pharmacy | Yes  
##  PatientMetadata 
The `PatientMetadata` dataclass represents a custom key-value pair for a patient.
Attribute | Type | Description | Required  
---|---|---|---  
`key` | `str` | The key of the metadata | Yes  
`value` | `str` | The value of the metadata | Yes  
##  Implementation Details 
  - **Creation** : Creates new patient records. By default the server generates the patient id, but you may supply your own `patient_id` — see Supplying a patient id on creation
  - **Updates** : Updates existing patient records when `patient_id` is provided
  - Validates that referenced practice locations exist in the system
  - Verifies that referenced healthcare providers exist in the system
  - Structures contact information through the `PatientContactPoint` dataclass
  - Structures the patient's contacts through the `PatientContact` dataclass, added or modified per entry according to `contact_identifier` — see Managing patient contacts
  - Structures external identifier through the `PatientExternalIdentifier` dataclass
  - Structures address information through the `PatientAddress` dataclass
  - Structures metadata through the `PatientMetadata` dataclass
##  Example Usage 
###  Creating a patient 
    ```python
    from canvas_sdk.effects.patient import Patient, PatientContactPoint, PatientExternalIdentifier, PatientMetadata
    from canvas_sdk.handlers.base import BaseHandler
    from canvas_sdk.v1.data.common import ContactPointSystem, ContactPointUse, PersonSex
    import datetime
    class MyHandler(BaseHandler):
        def compute(self):
            patient = Patient(
                first_name="Jane",
                last_name="Doe",
                middle_name="Marie",
                birthdate=datetime.date(1980, 1, 15),
                sex_at_birth=PersonSex.SEX_FEMALE,
                nickname="Janie",
                default_location_id="location-uuid",
                default_provider_id="provider-uuid",
                contact_points=[
                    PatientContactPoint(
                        system=ContactPointSystem.PHONE,
                        value="555-123-4567",
                        use=ContactPointUse.MOBILE,
                        rank=1,
                        has_consent=True
                    ),
                    PatientContactPoint(
                        system=ContactPointSystem.EMAIL,
                        value="jane.doe@example.com",
                        use=ContactPointUse.WORK,
                        rank=2,
                        has_consent=True
                    )
                ],
                external_identifiers=[
                    PatientExternalIdentifier(
                        system="http://www.aaa.com",
                        value="pat_id_123456"
                    )
                ],
                metadata = [
                    PatientMetadata(key="source", value="plugin"),
                    PatientMetadata(key="created_on", value=datetime.datetime.now().isoformat())
                ]
            )
            return [patient.create()]
    ```
###  Updating a patient 
    ```python
    from canvas_sdk.effects.patient import Patient, PatientAddress, PatientExternalIdentifier
    from canvas_sdk.handlers.base import BaseHandler
    from canvas_sdk.v1.data.common import AddressUse
    class MyHandler(BaseHandler):
        def compute(self):
            # Update an existing patient
            updated_patient = Patient(
                patient_id="existing-patient-uuid",
                first_name="Jane",
                last_name="Smith",  # Changed last name
                addresses=[
                    PatientAddress(
                        line1="456 Updated Street",
                        line2="Suite 200",
                        city="Updated City",
                        state_code="CA",
                        postal_code="90210",
                        country="US",
                        use=AddressUse.HOME
                    )
                ],
                external_identifiers=[
                    PatientExternalIdentifier(
                        system="http://www.updated-system.com",
                        value="new_patient_id_789"
                    )
                ]
            )
            return [updated_patient.update()]
    ```
###  Marking a Patient as Inactive or Deceased 
    ```python
    from canvas_sdk.effects.patient import Patient
    from canvas_sdk.handlers.base import BaseHandler
    import datetime
    class MyHandler(BaseHandler):
        def compute(self):
            # Mark a patient as inactive
            inactive_patient = Patient(
                patient_id="existing-patient-uuid",
                active=False
            )
            return [inactive_patient.update()]
    class DeceasedPatientHandler(BaseHandler):
        def compute(self):
            # Record a patient's death
            deceased_patient = Patient(
                patient_id="existing-patient-uuid",
                deceased=True,
                deceased_datetime=datetime.datetime(2025, 3, 14, 12, 0, 0),
                deceased_cause="Natural causes",
                deceased_comment="Pronounced at home."
            )
            return [deceased_patient.update()]
    ```
##  Supplying a patient id on creation 
By default, Canvas generates the patient id (`patient_id`) when you create a patient. You can supply your own instead by passing a 32-character hex string (a UUID4 with its hyphens removed) in the `patient_id` parameter of `Patient`. This lets your plugin generate the id up front and reuse it for follow-up, patient-scoped effects — such as notes or commands — in the same plugin execution, without reading the id back first. It works the same way Notes and Commands accept a pre-generated id.
A supplied id must be a well-formed patient id: a 32-character lowercase hex string, which is a UUID4 with its hyphens removed. Use `generate_patient_id()` to produce one rather than building the format by hand. An id in any other format — for example, a hyphenated or uppercase UUID — raises a validation error on `create()`, as does an id that already belongs to an existing patient. Since `generate_patient_id()` returns a fresh, well-formed id, it satisfies both requirements. If you omit `patient_id`, the server generates the id as before, so existing plugins are unaffected.
Because you generate the id up front, you can also return it to the caller from a [SimpleAPI](/sdk/handlers-simple-api-http/) endpoint — so a client creating the patient gets the id back in the response instead of having to look it up afterward. This example authenticates with the [`APIKeyAuthMixin`](/sdk/handlers-simple-api-http/), which expects a `simpleapi-api-key` secret declared in your manifest:
    ```python
    from canvas_sdk.effects.patient import Patient, generate_patient_id
    from canvas_sdk.effects.simple_api import JSONResponse, Response
    from canvas_sdk.handlers.simple_api import APIKeyAuthMixin, SimpleAPIRoute
    class CreatePatientAPI(APIKeyAuthMixin, SimpleAPIRoute):
        PATH = "/patients"
        def post(self) -> list[Response]:
            body = self.request.json()
            new_patient_id = generate_patient_id()
            patient = Patient(
                patient_id=new_patient_id,
                first_name=body["first_name"],
                last_name=body["last_name"],
            )
            # `new_patient_id` can be reused for follow-up patient-scoped effects in
            # the same execution, and is returned so the caller has it immediately
            # without a follow-up lookup.
            return [
                patient.create(),
                JSONResponse({"patient_id": new_patient_id}, status_code=201),
            ]
    ```
##  Managing patient contacts 
The `contacts` field writes the patient's contacts — emergency contacts, next-of-kin, and other related persons. What happens to each entry is decided by **`contact_identifier`** , not by whether you called `create()` or `update()`:
`contact_identifier` | `inactive` | Result  
---|---|---  
omitted | omitted | The contact is **added**  
supplied | omitted | The contact it names is **modified**  
supplied | `True` | The contact it names is **removed**  
omitted | `True` | Validation error — there is no contact to remove  
So `Patient(...).update()` adds a contact to a patient that already exists, which is the usual case for a plugin populating contacts after intake. Re-sending an identical contact matches the existing one rather than adding a second, so a handler that re-emits the same contact on every event will not accumulate duplicates. On an update, a `contact_identifier` that names no contact on that patient is treated as a mistake and raises rather than being added.
Contacts you leave out of the list are **left alone**. Unlike `addresses`, this field is not replace-based: omitting a contact never deletes it, and removal is always explicit through `inactive`.
An update writes only the fields you send, so changing a phone number does not blank the email or the comments. `name` (or `related_patient`) is the exception — every contact that is not a removal needs one, so resend the existing value when you are changing something else. Pass an empty string to clear a stored value deliberately.
    ```python
    from canvas_sdk.effects.patient import Patient, PatientContact, PatientContactCategory
    from canvas_sdk.handlers.base import BaseHandler
    from canvas_sdk.v1.data import ContactCategory
    class MyHandler(BaseHandler):
        def compute(self):
            # Look the coding up rather than composing one — an unknown coding raises.
            emergency = ContactCategory.objects.get(code="EMC")
            category = PatientContactCategory(
                code=emergency.code,
                code_system=emergency.system,
                name=emergency.name,
            )
            # No contact_identifier, so this adds a contact.
            patient = Patient(
                patient_id="existing-patient-id",
                contacts=[
                    PatientContact(
                        name="Jane Doe",
                        phone_number="5551234567",
                        email="jane@example.com",
                        comments="Primary emergency contact",
                        categories=[category],
                    )
                ],
            )
            return [patient.update()]
    ```
###  Linking one patient to another 
Setting `related_patient` to another patient's key makes that patient the contact. Because your plugin can supply the patient id on creation, it knows the key before the patient exists — so it can create a patient and reference it from a later effect in the same execution:
    ```python
    from canvas_sdk.effects.patient import Patient, PatientContact, generate_patient_id
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        def compute(self):
            spouse_key = generate_patient_id()
            spouse = Patient(
                patient_id=spouse_key,
                first_name="Alex",
                last_name="Doe",
            )
            # References a patient the previous effect creates. Effects are applied in
            # order, so the key resolves by the time this one is written.
            patient = Patient(
                patient_id="existing-patient-id",
                contacts=[
                    PatientContact(
                        related_patient=spouse_key,
                        comments="Spouse — also a patient in Canvas",
                    )
                ],
            )
            return [spouse.create(), patient.update()]
    ```
A `related_patient` contact carries no name of its own; Canvas shows the referenced patient's details instead.
###  Removing a contact 
A removal needs the `contact_identifier` of the contact to remove and nothing else — no name or related patient, since neither is meaningful on a delete. Read the identifier from the [PatientContactPerson](/sdk/data-patient/#patientcontactperson) data model:
    ```python
    from canvas_sdk.effects.patient import Patient, PatientContact
    from canvas_sdk.handlers.base import BaseHandler
    from canvas_sdk.v1.data import PatientContactPerson
    class MyHandler(BaseHandler):
        def compute(self):
            patient_key = "existing-patient-key"
            contact = PatientContactPerson.objects.filter(
                patient__id=patient_key, name="Jane Doe"
            ).first()
            if contact is None:
                return []
            patient = Patient(
                patient_id=patient_key,
                contacts=[
                    PatientContact(contact_identifier=str(contact.id), inactive=True)
                ],
            )
            return [patient.update()]
    ```
A single `contacts` list may mix all of these — additions, modifications and removals travel together in one effect.
##  Setting Race and Ethnicity 
`biological_race_codes` and `cultural_ethnicity_codes` each accept a list of code strings drawn from the [CDC Race and Ethnicity CodeSystem (CDCREC)](https://hl7.org/fhir/us/core/STU3.1.1/CodeSystem-cdcrec.html) — the same code set used by the [FHIR Patient API](/api/patient/). You can set both fields when creating or updating a patient, and you can supply more than one code per field.
Canvas recognizes the full CDCREC code set — both the OMB top-level categories below and the more specific detailed codes that roll up to them (for example, the race code `2108-9` "European" rolls up to `2106-3` "White", and the ethnicity code `2148-5` "Mexican" rolls up to `2135-2` "Hispanic or Latino"). The categories below are the most common values; see the CodeSystem for the complete list of detailed codes.
**Race** (`biological_race_codes`) — OMB top-level categories:
Code | Description  
---|---  
`1002-5` | American Indian or Alaska Native  
`2028-9` | Asian  
`2054-5` | Black or African American  
`2076-8` | Native Hawaiian or Other Pacific Islander  
`2106-3` | White  
`2131-1` | Other Race  
**Ethnicity** (`cultural_ethnicity_codes`) — OMB top-level categories:
Code | Description  
---|---  
`2135-2` | Hispanic or Latino  
`2186-5` | Not Hispanic or Latino  
    ```python
    from canvas_sdk.effects.patient import Patient
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        def compute(self):
            patient = Patient(
                patient_id="existing-patient-uuid",
                biological_race_codes=["2106-3"],      # White
                cultural_ethnicity_codes=["2186-5"]    # Not Hispanic or Latino
            )
            return [patient.update()]
    ```
##  Validation 
The effect performs validation before execution to ensure data integrity:
  1. **Required Fields** : 
     - For creation: Validates that mandatory fields like `first_name` and `last_name` are provided
     - For updates: Requires `patient_id` to be provided and verifies the patient exists in the database
  2. **Referenced Entity Validation** : Confirms that any referenced entities exist in the system: 
     - Verifies that the specified default practice location exists
     - Ensures that the specified default provider exists
  3. **Data Format Validation** : Ensures that provided values conform to expected formats: 
     - Date fields must be valid dates
     - Enumerated types like `PersonSex`, `ContactPointSystem`, and `ContactPointUse` must contain valid values
     - On creation, if `patient_id` is supplied it must be a well-formed patient id (a 32-character hex string); otherwise validation raises
     - On creation, a supplied `patient_id` must not already belong to an existing patient; a duplicate id raises a validation error
  4. **Update-Specific Validation** : 
     - Validates that the patient exists before attempting updates
  5. **Contact Validation** (see Managing patient contacts): 
     - Every contact that is not a removal must carry either `name` or `related_patient`
     - A removal (`inactive=True`) must carry `contact_identifier`
     - `contact_identifier` and `related_patient` must be UUIDs; on an update, `contact_identifier` must name a contact that belongs to this patient, and `related_patient` must name an existing patient
     - `phone_number` must be exactly 10 digits, and `email` must be a valid email address
     - `PatientContactCategory` requires `code`, `code_system` and `name`, and the coding must already exist in the instance — an unknown coding raises rather than being created
----- END PAGE https://docs.canvasmedical.com/sdk/effect-patient/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-protocol-cards/
Protocol cards appear on the right-hand-side of a patient's chart, and can be accessed by clicking on the Protocols filter button in the filter menu.
![protocol card](/assets/images/protocol-card.png)
A Protocol card consists of three main parts:
  - A title, which appears at the top in bold
  - A narrative, which appears just below the title to add any additional clarifying information
  - A list of recommendations, which each have a title and optionally a button that can either: 
    - open a new tab and navigate to another site
    - insert commands into a note
Name | Type | Required | Description  
---|---|---|---  
`patient_id` | _string_ | `true` (if `patient_filter` is not included) | The id of the [patient](/sdk/data-patient/)  
`patient_filter` | _dict_ | `true` (if `patient_id` is not included) | Patient queryset filters to apply the effect to multiple patients. For example, `{"active": True}` will apply to the effect to all active patients  
`key` | _string_ | `true` | A unique identifier for the protocol card  
`title` | _string_ | `true` | The title for the protocol card, which appears at the top in bold  
`narrative` | _string_ | `false` | The narrative for the protocol card, which appears just below the title  
`can_be_snoozed` | _boolean_ | `false` | Whether the protocol card can be snoozed, defaults to `false`  
`status` | Status | `false` | The status of the protocol card, defaults to `Status.DUE`  
`recommendations` | list[Recommendation] | `false` | The recommendations to appear in the protocol card  
`feedback_enabled` | _boolean_ | `false` | Whether users can provide feedback for the protocol card in Settings, defaults to `false`  
`due_in` | _integer_ | `false` | The number of days until the protocol card will be considered due for the patient, defaults to `-1` for already due  
|  |  |   
###  Recommendation 
Attribute | Type | Required | Description  
---|---|---|---  
`title` | _string_ | `true` | The description of the recommendation  
`button` | _string_ | `false` | The text to appear on the button  
`href` | _string_ | `false` | The url for the button to navigate to  
`commands` | list[Command] | `false` | The commands to be inserted  
###  Status 
Enum | Value  
---|---  
`DUE` | due  
`SATISFIED` | satisfied  
`NOT_APPLICABLE` | not_applicable  
`PENDING` | pending  
`NOT_RELEVANT` | not_relevant  
|   
To include a command recommendation you can:
  - import the command from the [commands module](/sdk/commands/), instantiate the command with all the values you wish to populate, and then call `.recommend(title: str = "", button: str | None)` on the command to generate the recommendation that you can append to the protocol card's recommendations. Keep in mind that, at the moment, not all commands are supported for command insertion. See below for the list of supported commands.
  - instantiate the command as above, and then pass it in a list to the `commands` attribute of a recommendation.
</br> </br>
For non-command recommendations, you can either use the `Recommendation` class, or the `.add_recommendation(title: str = "", button: str = "", href: str | None)` method on the protocol card.
**Example** :
    ```python
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from datetime import date
    from canvas_sdk.effects.protocol_card import ProtocolCard, Recommendation
    from canvas_sdk.commands import DiagnoseCommand, PlanCommand
    class MyHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.PATIENT_UPDATED)
        def compute(self):
            diagnose = DiagnoseCommand(
                icd10_code="I10",
                background="feeling bad for many years",
                approximate_date_of_onset=date(2020, 1, 1),
                today_assessment="still not great",
            )
            plan = PlanCommand(
                narrative="Follow up in 2 weeks",
            )
            p = ProtocolCard(
                patient_id=self.target,
                key="testing-protocol-cards",
                title="This is a ProtocolCard title",
                narrative="this is the narrative",
                status=ProtocolCard.Status.DUE,
                recommendations=[
                  Recommendation(title="this recommendation has no action, just words!"),
                  Recommendation(title="this recommendation inserts multiple commands", button="add commands", commands=[diagnose, plan])
                ],
            )
            p.add_recommendation(
                title="this is a recommendation", button="go here", href="https://canvasmedical.com/"
            )
            p.recommendations.append(diagnose.recommend(title="this inserts a diagnose command"))
            p.recommendations.append(title="new recommendation", button="start", commands=[diagnose])
            return [p.apply()]
    ```
To apply the effect to all active patients on plugin create and plugin update, you would include the plugin create and update events in `RESPONDS_TO`. And when responding to one of the plugin events you would use `patient_filter` instead of `patient_id` for the ProtocolCard.
    ```python
    from canvas_sdk.effects.protocol_card import ProtocolCard, Recommendation
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from datetime import date
    from canvas_sdk.effects.protocol_card import ProtocolCard, Recommendation
    from canvas_sdk.commands import DiagnoseCommand
    class MyHandler(BaseHandler):
        RESPONDS_TO = [
            EventType.Name(EventType.PATIENT_UPDATED),
            EventType.Name(EventType.PLUGIN_CREATED),
            EventType.Name(EventType.PLUGIN_UPDATED),
        ]
        def compute(self):
            p = ProtocolCard(
                key="testing-protocol-cards",
                title="This is a ProtocolCard title",
                narrative="this is the narrative",
                can_be_snoozed=True,
                recommendations=[
                    Recommendation(title="this recommendation has no action, just words!")
                ],
            )
            p.add_recommendation(
                title="this is a recommendation", button="go here", href="https://canvasmedical.com/"
            )
            diagnose = DiagnoseCommand(
                icd10_code="I10",
                background="feeling bad for many years",
                approximate_date_of_onset=date(2020, 1, 1),
                today_assessment="still not great",
            )
            p.recommendations.append(diagnose.recommend(title="this inserts a diagnose command"))
            if self.event.type in [EventType.PLUGIN_CREATED, EventType.PLUGIN_UPDATED]:
                p.patient_filter = {"active": True}
            else:
                p.patient_id = self.target
            return [p.apply()]
    ```
###  Supported Commands 
The following commands from the [commands module](/sdk/commands/) are currently supported for insertion from Protocol Cards:
  - Allergy
  - Assess
  - Diagnose
  - FollowUp
  - Goal
  - HistoryOfPresentIllness
  - Image
  - Immunize
  - Instruct
  - LabOrder
  - MedicationStatement
  - Perform
  - Plan
  - Prescribe
  - Questionnaire
  - ReasonForVisit
  - Refer
  - StructuredAssessment
  - Task
  - ValidateCodingGap
  - Vitals
----- END PAGE https://docs.canvasmedical.com/sdk/effect-protocol-cards/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-questionnaires/
The Canvas SDK includes functionality for handling questionnaire-related events.
##  Creating a Questionnaire 
Creating a questionnaire via the SDK requires current requires defining a YAML template and referencing it in your `CANVAS_MANIFEST.json` file. Read more [here](/sdk/questionnaires/).
##  Creating a Questionnaire Result 
The `CreateQuestionnaireResult` effect allows you to create custom scoring of questionnaires in Canvas. It adds a narrative to the command in the UI and can appear in the Social Determinants section of the left side of the chart if the questionnaire is configured to show in that section (see [here](/sdk/questionnaires) for how to control setting `display_result_in_social_history_section` for questionnaires).
###  Attributes 
Attribute | Required | Type | Description  
---|---|---|---  
interview_id | Yes | string | The id of the interview to associate the result with.  
score | Yes | float | The numerical score of the questionnaire result.  
abnormal | No | bool | Whether the result is considered abnormal. Defaults to `False`.  
narrative | No | string | A text description of the result and any recommended follow-up actions. Defaults to an empty string.  
code_system | Yes* | string | The code system used to identify the questionnaire (e.g., `"INTERNAL"`).  
code | Yes* | string | The code identifying the questionnaire within the code system (e.g., `"mchat_scoring"`).  
*Note: Questionnaire Results create an associated Observation record. The `code` and `code_system` fields are required in order to distinguish the Observation results.
###  Example 
**Note:** This example assumes that an M-CHAT questionnaire created and loaded into the Canvas instance.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.questionnaire_result import CreateQuestionnaireResult
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.v1.data.command import Command
    class MChatQuestionnaireResult(BaseHandler):
        """
        Return a CreateQuestionnaireResult effect in response to a committed Questionnaire Command that
        contains questions coded for the M-CHAT questionnaire.
        """
        RESPONDS_TO = [EventType.Name(EventType.QUESTIONNAIRE_COMMAND__POST_COMMIT)]
        MCHAT_CODE_SYSTEM = "INTERNAL"
        MCHAT_CODE = "mchat_scoring"
        def compute(self) -> list[Effect]:
            # Get the interview object, which will be the anchor object on the Questionnaire command.
            command = Command.objects.get(id=self.event.target.id)
            interview = command.anchor_object
            if not interview.committer:
                return []
            # Return no effects if the interview has no questions that are coded as M-CHAT questions
            if not any(
                q.code == self.MCHAT_CODE and q.code_system == self.MCHAT_CODE_SYSTEM
                for q in interview.questionnaires.all()
            ):
                return []
            # sum up the numerical value of each answered questionnaire
            score = 0
            for response in interview.interview_responses.all():
                score = score + int(response.response_option.value)
            # Determine the narrative and whether the result is abnormal
            if score >= 0 and score <= 2:
                abnormal = False
                narrative = (
                    "The score is LOW risk. Child has screened negative. No immediate follow-up is "
                    "needed. However, the child should be rescreened at 24 months or after 3 months "
                    "have passed if they are younger than 2 years. Monitoring the child's "
                    "development remains important."
                )
            elif score >= 3 and score <= 7:
                abnormal = True
                narrative = (
                    "The score is MODERATE risk. Administer the M-CHAT-R Follow-Up items that "
                    "correspond to the at-risk responses. Only those items which were scored at risk "
                    "need to be completed. If 2 or more items continue to be at-risk, refer the "
                    "child immediately for (a) early intervention and (b) diagnostic evaluation."
                )
            elif score >= 8 and score <= 20:
                abnormal = True
                narrative = (
                    "The score is HIGH risk. It is not necessary to complete the M-CHAT-R Follow-Up "
                    "at this time. Bypass Follow-Up, and refer immediately for (a) early "
                    "intervention and (b) diagnostic evaluation."
                )
            else:
                abnormal = True
                narrative = "Error occurred trying to score questionnaire."
            # Create and return the effect
            effect = CreateQuestionnaireResult(
                interview_id=str(interview.id),
                score=score,
                abnormal=abnormal,
                narrative=narrative,
                code_system=self.MCHAT_CODE_SYSTEM,
                code=self.MCHAT_CODE,
            )
            return [effect.apply()]
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/effect-questionnaires/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-redirect/
The `RedirectEffect` tells the Canvas frontend to navigate the browser to a destination. The plugin returns the effect from a handler and the frontend performs a full-page navigation. The headline use case is sending a user onward after a note is signed — for example, navigating back to a work queue to pick up the next patient.
A redirect is delivered only to the **acting user** who triggered the handler — and only to that user's browser. Because of that, it takes effect only when the handler runs in the context of a real user with an active browser session. Return it from user-initiated handlers — a note state-change (sign/lock) handler, an action-button handler, an application handler, or an authenticated [SimpleAPI](/sdk/handlers-simple-api/) call. If a handler has no user actor — for example a `CronTask`, other background processing, or any event whose actor defaults to canvas-bot — there is no browser to navigate and the redirect is silently ignored. See [Event Actor](/sdk/events/#event-actor) for which events carry an actor.
Provide **exactly one** destination:
  - `url` — a full URL string the plugin composes in Python (it may include patient/note ids). Either an external URL (`https://...`) or an internal Canvas path (`/panel`, `/patient/{key}?noteId=...`).
  - `application_id` — the identifier of a Canvas application to open.
By default the navigation replaces the current tab. Set `target` to `RedirectEffect.TargetType.NEW_TAB` to open a `url` destination in a new tab instead.
> **Internal navigation must be a root-relative path that starts with`/`** — e.g. `url="/schedule"` (or `/panel`, `/patient/{key}?noteId=...`). A leading-slash path is the _only_ form treated as internal navigation. A bare page name like `schedule` will **not** work: anything that doesn't start with `/` is treated as an external URL and is rejected unless it's a full `https://...` URL (protocol-relative `//...` and backslash `/\...` values are always rejected). The matching `REDIRECT_ALLOWLIST_INTERNAL` entries must likewise be leading-slash paths (e.g. `/schedule`).
##  Attributes 
Name | Type | Required | Description  
---|---|---|---  
`url` | `str` | Yes* | A full external URL (`https://...`) or an internal Canvas path that **must start with`/`** (e.g. `/schedule`, `/patient/{key}`), composed by the plugin. Non-empty.  
`application_id` | `str` | Yes* | The identifier of a Canvas application to open. Must exist and be enabled.  
`target` | `TargetType` | No | Where to open a `url` destination. Defaults to `TargetType.SAME_TAB`.  
***** Provide **exactly one** of `url` or `application_id` — they are mutually exclusive.
##  `TargetType`
A `StrEnum` of the supported navigation targets. You can also pass the string value.
Member | Value | Behavior  
---|---|---  
`RedirectEffect.TargetType.SAME_TAB` | `"same_tab"` | Replaces the current EHR view (full-page navigation). The default.  
`RedirectEffect.TargetType.NEW_TAB` | `"new_tab"` | Opens the destination in a new browser tab.  
##  Security & Allowlist 
Every destination is validated **on the server** before the browser navigates — the frontend is never trusted to decide whether a target is allowed. This blocks open-redirect abuse and accidental leakage of PHI through query parameters. **Targets are denied by default.**
The allowlist governs only _where a plugin may send a user_ — it does **not** change what that user is allowed to see, and cannot be used to bypass their permissions. A redirect performs an ordinary browser navigation, so the destination still enforces the user's own access: redirecting a user to a page or application they lack permission for behaves exactly as if they navigated there themselves (they're denied by that destination), and never elevates their access.
The allowlist is configured **per instance by an administrator** via three plugin secrets. Your plugin declares the keys in its manifest `variables`; the admin sets each value on the Plugin admin page, or from the CLI with [`canvas config set`](/sdk/canvas_cli/#canvas-config-set). (This redirect allowlist is separate from the manifest's `url_permissions` field, which allow-lists iframe and script domains for layout effects — the two are unrelated.) Each value is a list with **one entry per line** — entries are newline-delimited, not comma-separated, because URLs and paths can legitimately contain commas (e.g. `?q=1,2,3`):
Secret key | Value (one entry per line) | Permits  
---|---|---  
`REDIRECT_ALLOWLIST_INTERNAL` | `/patients`  
`/panel`  
`/patient` | those path roots and anything the plugin composes under them, matched at a path boundary (`/patient/{key}?noteId=...`).  
`REDIRECT_ALLOWLIST_EXTERNAL` | `https://app.example.com` | those origins/prefixes (**include the scheme**), matched **case-insensitively** at an origin/path boundary — so it does **not** match `https://app.example.com.evil.com`, and a differing port (e.g. `https://app.example.com:8443/...`) is not a match.  
`REDIRECT_ALLOWLIST_APPLICATION` | `my_plugin.applications.app:MyApp` | redirecting to those applications by id (matched exactly; the app must exist and be enabled).  
Declare the keys in your manifest so the admin can fill them:
    ```json
    {
      "variables": [
        { "name": "REDIRECT_ALLOWLIST_INTERNAL" },
        { "name": "REDIRECT_ALLOWLIST_EXTERNAL" },
        { "name": "REDIRECT_ALLOWLIST_APPLICATION" }
      ]
    }
    ```
Both steps are required, and both default to "blocked": if you don't **declare** a key in the manifest, the admin has no field to fill; if the admin doesn't **set** a value, that key's allowlist is empty. An empty or absent secret allows nothing — so each redirect category (internal / external / application) only works once its key is declared _and_ an admin has given it a value. A freshly installed plugin can therefore redirect nowhere until an admin opts it in.
Set a value from the CLI with your shell's newline quoting so each entry stays on its own line (see [`canvas config set`](/sdk/canvas_cli/#canvas-config-set)):
    ```console
    $ canvas config set my_plugin $'REDIRECT_ALLOWLIST_INTERNAL=/panel\n/patient'
    ```
Non-allowlisted destinations are dropped, and the platform logs only the plugin name and the blocked host (never the full URL/path). Protocol-relative (`//host`) and backslash (`/\host`) targets are always rejected.
##  Example Usage 
###  Redirect to a work queue after a note is signed 
Requires `/panel` in the plugin's `REDIRECT_ALLOWLIST_INTERNAL` secret.
    ```python
    from canvas_sdk.effects.redirect import RedirectEffect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.v1.data.note import CurrentNoteStateEvent, NoteStates
    class RedirectAfterSign(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.NOTE_STATE_CHANGE_EVENT_CREATED)
        def compute(self):
            state = CurrentNoteStateEvent.objects.values_list("state", flat=True).get(
                id=self.event.target.id
            )
            if state != NoteStates.LOCKED:
                return []
            # Send the provider back to their work queue to grab the next patient.
            return [RedirectEffect(url="/panel").apply()]
    ```
###  Open an external URL in a new tab from an action button 
Requires `https://app.example.com` in the plugin's `REDIRECT_ALLOWLIST_EXTERNAL` secret.
    ```python
    return [
        RedirectEffect(
            url="https://app.example.com/orders/next",
            target=RedirectEffect.TargetType.NEW_TAB,
        ).apply()
    ]
    ```
###  Redirect to an application by id 
Requires the identifier in the plugin's `REDIRECT_ALLOWLIST_APPLICATION` secret.
    ```python
    return [RedirectEffect(application_id="my_plugin.applications.app:MyApp").apply()]
    ```
###  Redirect from an application iframe 
An application iframe can't return an effect directly. The clean pattern is to expose a [SimpleAPI](/sdk/handlers-simple-api/) endpoint on your plugin that returns a `RedirectEffect`, and have the iframe `fetch()` it. Because a SimpleAPI request is authenticated as the acting user, the returned effect is validated and delivered through the **exact same** interpreter → allowlist → per-user path as an action-button or note-sign redirect — there is no iframe-specific code path to reason about.
> **Why an API call and not`postMessage`?** An iframe could `postMessage` its parent window to request a redirect (the way the close-modal workflow does), but we recommend against it here. A redirect already has to make a server round-trip for allowlist validation, so routing the request through the parent window and a dedicated mutation would add a second mechanism that buys nothing. Having the iframe call your own API that returns the effect is cleaner:
> 
>   - **One mechanism, one mental model.** The iframe reuses the same effect pipeline as every other redirect — no separate frontend bridge, no dedicated mutation, and target validation lives in exactly one place (the interpreter).
>   - **Secure by construction.** The plugin whose allowlist is checked is _intrinsic_ : it's the plugin that owns the API endpoint. Nothing frontend-supplied has to be trusted or proven un-spoofable — a `postMessage` bridge would first have to attribute the message to an owning application before it could even pick which allowlist to apply.
>   - **Composable.** Your endpoint can do real work first — persist state, branch on the patient/note, decide _where_ to send the user — and then return the redirect alongside a normal JSON response.
> 
**The endpoint** returns the `RedirectEffect` (optionally with a response body for the `fetch`):
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.redirect import RedirectEffect
    from canvas_sdk.effects.simple_api import JSONResponse, Response
    from canvas_sdk.handlers.simple_api import SimpleAPI, StaffSessionAuthMixin, api
    class MyAppAPI(StaffSessionAuthMixin, SimpleAPI):
        @api.post("/redirect")
        def redirect(self) -> list[Response | Effect]:
            # ...optionally do work first (persist data, decide the destination)...
            return [
                RedirectEffect(url="/panel").apply(),  # or application_id="my_plugin.applications.app:MyApp"
                JSONResponse({"ok": True}),
            ]
    ```
**The iframe** calls it with a credentialed, same-origin request:
    ```js
    // inside the plugin application iframe
    fetch('/plugin-io/api/my_plugin/redirect', {
      method: 'POST',
      credentials: 'same-origin',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ destination: 'panel' })
    });
    ```
**How the redirect arrives.** The navigation does **not** come back in the `fetch()` response body. The effect is broadcast to the acting user and applied by the frontend's redirect subscription, exactly as for any other `RedirectEffect` — so the `fetch` response is just your endpoint's acknowledgement, and the browser navigates a moment later when the effect is delivered.
**Requirements & gotchas**
  - The endpoint must authenticate the request **as a specific Canvas user** — that user is the actor the redirect targets. Use [`StaffSessionAuthMixin`](/sdk/handlers-simple-api/) (as above) or `PatientSessionAuthMixin`, which resolve the acting user from the Canvas session, or another scheme that identifies a specific user (for example an OAuth token tied to a user). A **shared-secret** scheme — `BasicAuthMixin` or `APIKeyAuthMixin` — authenticates the _request_ but establishes no acting user, so a redirect returned from it has no browser to target and is silently dropped.
  - For the session mixins, the request must be **same-origin and credentialed** (`credentials: 'same-origin'`) so the session is sent and the server can identify the acting user. Plugin-served iframes — rendered from `LaunchModalEffect` content or a plugin-served URL — are same-origin. An **unauthenticated** request has no acting user either, so the redirect is silently dropped.
  - The target still has to be **allowlisted** (see Security & Allowlist); the API path enforces the identical gate.
  - `target` (new tab) applies only to `url` destinations; an `application_id` always opens in-app.
##  Validation 
Construction is validated by Pydantic and will raise a `ValidationError` for:
  - Providing neither `url` nor `application_id`, or providing both.
  - An empty `url`.
  - A `target` that is not a member of `TargetType`.
  - An `application_id` that does not resolve to an existing application.
----- END PAGE https://docs.canvasmedical.com/sdk/effect-redirect/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-reload-action-buttons/
The reload action button effects let a plugin tell Canvas to recompute and re-render its [action buttons](/sdk/handlers-action-buttons/) without the user reloading the page. This is useful after your plugin changes state that a button's `visible()` method, title, or color depends on, so the displayed buttons reflect the new state.
There are two effects, one for each scope:
  - `ReloadNoteActionButtonsEffect` — reloads the action buttons for a single note.
  - `ReloadPatientActionButtonsEffect` — reloads the action buttons for a patient.
Emit one from any handler's `compute()` or `handle()` — not only from an `ActionButton`. The effect re-fires the relevant [`SHOW_*_BUTTON`](/sdk/events/#action-buttons-events) events, so every button in that location recomputes `visible()` from scratch: the button set is rebuilt, not patched.
##  When to reload 
A button's `visible()` result, its title, and its color are all computed from live data each time the location is evaluated. Reloading is how you push those changes to the footer or header without a full page refresh. Common cases:
  - **The button has done its job.** Once a button is clicked and its action completes, it often no longer applies — reload so its `visible()` re-evaluates to `False` and the button drops out of the set instead of lingering as a stale, re-clickable control.
  - **The label or color should change.** When a button reflects state — a title that shows a count of outstanding items, or a color that turns green once a task is complete — reload after that state changes so the button re-renders with its new title and color.
  - **Data the button depends on changed elsewhere.** After a command is committed, a note transitions to a new state, or related records are updated by another handler, reload the location so every button recomputes against the current data.
* * *
##  ReloadNoteActionButtonsEffect 
Re-evaluates the note's action buttons in the `NOTE_HEADER`, `NOTE_FOOTER`, and `NOTE_HEADER_DROPDOWN` locations. It also re-reads the note's [footer configuration](/sdk/effect-note-footer-configuration/) (by re-firing `NOTE_FOOTER__GET_CONFIGURATION`), so a plugin that toggles `hide_default_state_buttons` can refresh whether Canvas's native footer buttons are hidden without a full page reload.
###  Attributes 
Field | Type | Description  
---|---|---  
`id` | `str \| UUID` | The external id of a [Note](/sdk/data-note/#note) (`Note.id`). The note must exist, or the effect raises a validation error.  
> **Warning:** The `note_id` carried by a [`SHOW_*_BUTTON`](/sdk/events/#action-buttons-events) context is the note's **database id** (`dbid`), while this effect is keyed by the note's **external id**. Resolve between them through the [`Note`](/sdk/data-note/#note) data model — for example `Note.objects.filter(dbid=...).first().id`. 
###  Example 
This handler reloads a note's footer whenever any command is committed, so a button that hides while the note has uncommitted commands reappears the moment the last one is committed:
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.action_button import ReloadNoteActionButtonsEffect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers.base import BaseHandler
    from canvas_sdk.v1.data.command import Command
    class ReloadFooterOnCommandCommit(BaseHandler):
        RESPONDS_TO = [
            EventType.Name(value)
            for value in EventType.values()
            if EventType.Name(value).endswith("_COMMAND__POST_COMMIT")
        ]
        def compute(self) -> list[Effect]:
            command = Command.objects.filter(id=self.event.target.id).first()
            if not command or not command.note:
                return []
            return [ReloadNoteActionButtonsEffect(id=str(command.note.id)).apply()]
    ```
##  ReloadPatientActionButtonsEffect 
Re-evaluates the patient's action buttons in the `CHART_PATIENT_HEADER` location.
###  Attributes 
Field | Type | Description  
---|---|---  
`id` | `str` | The id of a [Patient](/sdk/data-patient/#patient). The patient must exist, or the effect raises a validation error.  
###  Example 
This handler refreshes a patient's header buttons whenever one of their tasks changes, so a `CHART_PATIENT_HEADER` button that shows a live count of open tasks stays current as tasks are created or completed:
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.action_button import ReloadPatientActionButtonsEffect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers.base import BaseHandler
    class ReloadPatientButtonsOnTaskChange(BaseHandler):
        RESPONDS_TO = [
            EventType.Name(EventType.TASK_CREATED),
            EventType.Name(EventType.TASK_UPDATED),
        ]
        def compute(self) -> list[Effect]:
            patient_id = (self.event.context.get("patient") or {}).get("id")
            if not patient_id:
                return []
            return [ReloadPatientActionButtonsEffect(id=patient_id).apply()]
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/effect-reload-action-buttons/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-send-contact-verification/
The `SendContactVerification` effect instructs Canvas to send a verification (for example, an email or SMS code) to a specific Patient Contact Point. Use it to verify a patient's email address or phone number — for example, before relying on that channel for outbound communications, or before enabling patient-portal features that require a verified contact channel. It applies to any patient contact point; it isn't tied to the patient portal.
Attribute | Type | Description  
---|---|---  
`contact_point_id` | `str` or `UUID` | The id of the [`PatientContactPoint`](/sdk/effect-patient/#patientcontactpoint) to verify.  
##  Validation & Errors 
When an effect is prepared, the model validates inputs and returns structured error details if something is invalid.
  - **Contact Point Exists** — The effect verifies the provided `contact_point_id` maps to an existing `PatientContactPoint` record. If no matching record exists the effect will include an error detail with message: `Patient Contact Point does not exist`.
##  Caveats 
  - Emitting this effect will trigger a save to the associated `PatientContactPoint`. If your plugin sends `SendContactVerification` in direct response to a `PATIENT_CONTACT_POINT_UPDATED` event, the save triggered by the effect can cause the same event to fire again, producing an infinite event loop. To avoid this, debounce or detect origin (for example, ignore updates originating from the plugin runner or set a transient flag on the model) before emitting the effect in response to contact point update events.
##  Example Usage 
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.send_contact_verification import SendContactVerificationEffect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class MyHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.PATIENT_CONTACT_POINT_CREATED)
        def compute(self) -> list[Effect]:
            contact_point_id = self.event.target.id
            verification_effect = SendContactVerificationEffect(contact_point_id=contact_point_id)
            return [verification_effect.apply()]
    ```
##  Notes 
  - This effect only triggers a verification send for the contact point. It does not mark the contact as verified — verification completion is handled by the platform when the patient completes the challenge.
  - The effect relies on `PatientContactPoint` existing in the database. If your integration creates contact points in the same operation, ensure they are persisted before emitting this effect.
----- END PAGE https://docs.canvasmedical.com/sdk/effect-send-contact-verification/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-service-provider/
The Service Provider effects let a plugin build and maintain its own directory of external providers. Providers created this way are readable through the [ServiceProvider](/sdk/data-serviceprovider/) data model — where they are flagged with `is_customer_managed` — and can be offered in the provider-search surfaces by [handling those searches yourself](/guides/customize-search-results/#offering-your-own-providers-alongside-the-directory).
##  Create Service Provider 
Creates a service provider, or updates a matching one.
###  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`first_name` | `str` | Provider name, or the organization name | Yes  
`specialty` | `str` | Free text | Yes  
`business_address` | `str` | Business address | Yes  
`last_name` | `str` or `None` | Omit for organizations | No  
`practice_name` | `str` or `None` | Practice or organization name | No  
`business_phone` | `str` or `None` | Business phone number | No  
`business_fax` | `str` or `None` | Business fax number | No  
`npi` | `str` or `None` | Exactly 10 digits | No  
`direct_address` | `str` or `None` | Up to 512 characters | No  
`notes` | `str` or `None` | Free-text notes | No  
`is_active` | `bool` | Defaults to `True` | No  
The required fields reject empty strings.
###  Calling create more than once 
Creating never produces a duplicate. These four fields together identify a provider:
  - `first_name`
  - `last_name`
  - `specialty`
  - `business_address`
If a provider already exists with the same values for all four, the create updates that provider rather than adding a second one. Only a provider that differs on at least one of them is created as a new record.
When an existing provider is matched:
  - only the fields you sent are written; the rest keep their current values
  - a deactivated provider stays deactivated unless you send `is_active=True`
Because of this, the same create is safe to run repeatedly — on a schedule, on every plugin install, or as a re-import of a directory you already loaded. An omitted or empty `last_name` is treated as the empty string when matching, so repeated creates for an organization resolve to the same record.
###  Example Usage 
    ```python
    from canvas_sdk.effects.service_provider import ServiceProvider
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers.base import BaseHandler
    class ProviderLoader(BaseHandler):
        RESPONDS_TO = [EventType.Name(EventType.PLUGIN_CREATED)]
        def compute(self):
            return [
                ServiceProvider(
                    first_name="Jane",
                    last_name="Doe",
                    specialty="Cardiology",
                    business_address="123 Main St",
                    business_fax="5555550100",
                    npi="1234567890",
                    direct_address="jane.doe@direct.example.org",
                ).create(),
                # An organization has no last name.
                ServiceProvider(
                    first_name="Acme Imaging Center",
                    specialty="Radiology",
                    business_address="1 Hospital Way",
                ).create(),
            ]
    ```
##  Update Service Provider 
Updates the provider with the given `id`. Only the fields you set are sent, so an update never clears a field you did not mention. `first_name` and `specialty` cannot be set to `None`.
    ```python
    ServiceProvider(id="d2194110-5c9a-4842-8733-ef09ea5ead11", notes="Prefers fax").update()
    ```
###  Reactivating a provider 
Set `is_active=True` explicitly. Nothing else reactivates a provider.
    ```python
    ServiceProvider(id="d2194110-5c9a-4842-8733-ef09ea5ead11", is_active=True).update()
    ```
##  Deactivate Service Provider 
Deactivates a provider without deleting it, so anything referencing it keeps working.
    ```python
    ServiceProvider(id="d2194110-5c9a-4842-8733-ef09ea5ead11").deactivate()
    ```
##  Reading providers back 
Use the [ServiceProvider data module](/sdk/data-serviceprovider/), and `is_customer_managed` to read only the providers your plugin created:
    ```python
    from canvas_sdk.v1.data.service_provider import ServiceProvider
    ServiceProvider.objects.filter(is_customer_managed=True, is_active=True)
    ```
To surface them in the Refer, Imaging Order, fax recipient, or external care team searches, see [Offering your own providers alongside the directory](/guides/customize-search-results/#offering-your-own-providers-alongside-the-directory).
To offer them in a provider search, see [`as_search_result` and `as_search_contact`](/sdk/data-serviceprovider/#search-results).
----- END PAGE https://docs.canvasmedical.com/sdk/effect-service-provider/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-staff-external-identifier/
Manage external identifiers on a staff member from a plugin. `StaffExternalIdentifier` is a single effect class with three methods — `.create()`, `.update()`, and `.delete()` — and which fields are required depends on the operation.
##  Methods 
###  create() → Effect 
Creates a new external identifier on the specified staff member.
####  Attributes 
Attribute | Type | Required | Description  
---|---|---|---  
`staff_id` | `str` / `UUID` | Yes | UUID of the [Staff](/sdk/data-staff/) record.  
`value` | `str` | Yes | The identifier value (e.g. an employee ID).  
`system` | `str` | No | The system the identifier belongs to (typically a URL).  
####  Validation 
  - `staff_id` must reference an existing Staff record, or the effect raises a descriptive error.
  - `id` must not be set on `create()` — the UUID is assigned server-side. Supplying it fails validation.
  - `value` and `staff_id` are required.
####  Server-side defaults 
Canvas applies these defaults on `create()`:
  - `use` → `"usual"`
  - `issued_date` → `"1970-01-01"`
  - `expiration_date` → `"2100-12-31"`
####  Example 
    ```python
    from canvas_sdk.effects.staff import StaffExternalIdentifier
    effect = StaffExternalIdentifier(
        staff_id="4150cd20de8a470aa570a852859ac87e",
        system="https://hr.example.com/",
        value="EMP-001234",
    ).create()
    ```
###  update() → Effect 
Updates fields on an existing external identifier. Only the fields you set on the effect are written; unset fields keep their existing values.
####  Attributes 
Attribute | Type | Required | Description  
---|---|---|---  
`id` | `str` / `UUID` | Yes | UUID of the identifier to update.  
`value` | `str` | No | New identifier value. Only written if supplied.  
`system` | `str` | No | New system value. Only written if supplied.  
####  Validation 
  - `id` is required and must reference an existing StaffExternalIdentifier record, or the effect raises a descriptive error.
####  Example 
    ```python
    from canvas_sdk.effects.staff import StaffExternalIdentifier
    effect = StaffExternalIdentifier(
        id="00000000-0000-0000-0000-000000000001",
        value="EMP-005678",
    ).update()
    ```
###  delete() → Effect 
Deletes the external identifier identified by `id`.
####  Attributes 
Attribute | Type | Required | Description  
---|---|---|---  
`id` | `str` / `UUID` | Yes | UUID of the identifier to delete.  
####  Validation 
  - `id` is required and must reference an existing StaffExternalIdentifier record, or the effect raises a descriptive error.
####  Example 
    ```python
    from canvas_sdk.effects.staff import StaffExternalIdentifier
    effect = StaffExternalIdentifier(
        id="00000000-0000-0000-0000-000000000001",
    ).delete()
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/effect-staff-external-identifier/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-staff-metadata/
The `StaffMetadata` effect provides a flexible key-value storage system for staff-specific data within the Canvas system, letting plugins attach extensible information beyond the standard staff data model.
##  Overview 
`StaffMetadata` exposes `.upsert(value)` to write or replace a metadata entry, and `.delete()` to remove one. The same key may be used across many staff members; the `(staff, key)` pair is unique per staff member.
##  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`staff_id` | `str` | Id of the [Staff](/sdk/data-staff/) record to associate metadata with | Yes  
`key` | `str` | Unique identifier for the metadata entry within the staff context | Yes  
##  Methods 
###  upsert(value: str) → Effect 
Creates or updates a metadata entry for the specified staff and key combination.
####  Parameters 
Parameter | Type | Description | Required  
---|---|---|---  
`value` | `str` | The metadata value to store | Yes  
####  Behavior 
  - If a metadata entry with the specified key already exists for the staff member, it will be updated with the new value.
  - If no entry exists, a new metadata entry will be created.
  - Metadata entries are isolated per staff member — the same key can hold different values for different staff members.
  - Values are stored as strings with no schema enforcement; the plugin is responsible for validating its own values.
####  Key Naming Conventions 
  1. **Use descriptive names**. Choose keys that clearly indicate the purpose of the metadata. 
     - Good: `department`, `cost_center`, `external_employee_id`
     - Avoid: `data1`, `temp`, `misc`
  2. **Namespace your keys**. Prefix keys for integrations or modules to avoid collisions. 
     - Example: `hr.employee_id`, `payroll.cost_center`
####  Value Storage 
  1. **String serialization**. All values are stored as strings. For complex data: 
         ```python
         import json
         from canvas_sdk.effects.staff_metadata import StaffMetadata
         metadata = StaffMetadata(
             staff_id="4150cd20de8a470aa570a852859ac87e",
             key="hr.profile",
         )
         complex_data = {"hire_date": "2020-01-15", "department": "cardiology"}
         metadata.upsert(json.dumps(complex_data))
         ```
  2. **Boolean values**. Store as `"true"` or `"false"` strings for consistency.
####  Examples 
    ```python
    from canvas_sdk.effects.staff_metadata import StaffMetadata
    # Tag a provider with their primary department
    metadata = StaffMetadata(
        staff_id="4150cd20de8a470aa570a852859ac87e",
        key="department",
    )
    effect = metadata.upsert("cardiology")
    ```
Mirroring an HR system from a handler:
    ```python
    from canvas_sdk.effects.staff_metadata import StaffMetadata
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.events import EventType
    class StaffHRSync(BaseHandler):
        """Sync select fields from an HR webhook payload onto Canvas staff."""
        RESPONDS_TO = EventType.Name(EventType.STAFF_UPDATED)
        def compute(self):
            staff_id = self.event.context["staff"]["id"]
            hr_record = self.event.context.get("fields", {}).get("hr_record", {})
            return [
                StaffMetadata(staff_id=staff_id, key=f"hr.{key}").upsert(str(value))
                for key, value in hr_record.items()
            ]
    ```
###  delete() → Effect 
Removes the metadata entry identified by `(staff_id, key)`.
####  Behavior 
  - Removes the row that matches both `staff_id` and `key`. Returns success even if no row was present (idempotent).
  - Does not affect other metadata entries for the same staff member with different keys.
####  Example 
    ```python
    from canvas_sdk.effects.staff_metadata import StaffMetadata
    # Clear the department tag for a staff member
    effect = StaffMetadata(
        staff_id="4150cd20de8a470aa570a852859ac87e",
        key="department",
    ).delete()
    ```
##  Validation 
The effect validates before execution:
  - **Staff existence** : the `staff_id` must correspond to an existing Staff record, or the effect raises a descriptive error.
  - **Required fields** : `staff_id` and `key` must be non-empty strings, and `.upsert(...)` requires a `value`.
----- END PAGE https://docs.canvasmedical.com/sdk/effect-staff-metadata/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-surescripts/
> **Warning:** **This feature must be enabled by Canvas.** To use the Surescripts effects, [contact Canvas Support](https://portal.usepylon.com/canvas-medical/forms/standard) to have these Surescripts effects enabled for your instance. Until it is enabled, these effects will not send requests. 
Surescripts effects let plugins query insurance eligibility, benefits, and medication history through Surescripts. Eligibility and benefits requests receive responses asynchronously as corresponding events; medication history is handled by Canvas without a plugin-facing response.
##  Eligibility 
Check a patient's insurance coverage and plan details. Send a request with `SendSurescriptsEligibilityRequestEffect`, then handle the `SURESCRIPTS_ELIGIBILITY_RESPONSE` event when the response arrives.
###  SendSurescriptsEligibilityRequestEffect 
Sends an eligibility request to Surescripts to check a patient's insurance coverage. The response arrives as a `SURESCRIPTS_ELIGIBILITY_RESPONSE` event.
####  Attributes 
Name | Type | Description  
---|---|---  
`patient_id` | `str` | The Canvas [Patient](/sdk/data-patient/#patient) ID for whom to check eligibility.  
`staff_id` | `str` | The Canvas [Staff](/sdk/data-staff/#staff) ID initiating the request.  
`correlation_id` | `str` | A unique identifier for matching the response to this request. Auto-generated if not provided. Read this value after instantiation and store it for later use.  
####  Correlation ID 
Each eligibility request includes a `correlation_id` that echoes back in the corresponding `SURESCRIPTS_ELIGIBILITY_RESPONSE` event. Use this to match responses to their originating requests when handling multiple concurrent eligibility checks.
By default, the effect auto-generates a unique `correlation_id` (a UUID hex string). You can pass your own value if you need to thread external state through the request-response cycle.
> **Note:** The `correlation_id` is required for receiving response events. The platform only delivers `SURESCRIPTS_ELIGIBILITY_RESPONSE` events to plugins that sent a request with a valid `correlation_id`.
####  Example Usage 
    ```python
    from canvas_sdk.effects.surescripts.surescripts_messages import SendSurescriptsEligibilityRequestEffect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers.base import BaseHandler
    class CheckEligibilityOnAppointment(BaseHandler):
        RESPONDS_TO = [EventType.Name(EventType.APPOINTMENT_CREATED)]
        def compute(self):
            patient_id = self.event.target.get("id")
            staff_id = self.event.context.get("created_by", {}).get("id")
            effect = SendSurescriptsEligibilityRequestEffect(
                patient_id=patient_id,
                staff_id=staff_id,
            )
            # Store the correlation_id to match the response later
            # For example, save it to custom data or cache
            correlation_id = effect.correlation_id
            return [effect.apply()]
    ```
###  Handling Eligibility Responses 
When Surescripts returns an eligibility response, the platform fires a `SURESCRIPTS_ELIGIBILITY_RESPONSE` event. Use the typed data classes from `canvas_sdk.events.surescripts` to parse the response.
> **Important:** To prevent infinite loops, you cannot return a `SendSurescriptsEligibilityRequestEffect` from a handler that responds to `SURESCRIPTS_ELIGIBILITY_RESPONSE` events.
####  Response Data Classes 
#####  SurescriptsEligibilityResponse 
The top-level response object containing eligibility results.
Name | Type | Description  
---|---|---  
`correlation_id` | `str` | The correlation ID from the originating request.  
`patient_id` | `str` | The Canvas [Patient](/sdk/data-patient/#patient) ID for this eligibility check.  
`plans` | EligibilityPlan[] | List of insurance plans returned in the response.  
`error` | `str` or `None` | Error message if the request failed, otherwise `None`.  
#####  EligibilityPlan 
Represents a single insurance plan from the eligibility response.
Name | Type | Description  
---|---|---  
`pbm_name` | `str` | Name of the Pharmacy Benefit Manager.  
`payer_id` | `str` | Identifier for the insurance payer ([Transactor](/sdk/data-coverage/#transactor)).  
`member_id` | `str` | The patient's member ID for this plan.  
`plan_network_id` | `str` or `None` | Network identifier for the plan.  
`group_number` | `str` or `None` | Group number for the plan.  
`drug_formulary_number` | `str` or `None` | Drug formulary identifier.  
`coverage_id` | `str` or `None` | [Coverage](/sdk/data-coverage/#coverage) identifier.  
`description` | `str` or `None` | Human-readable description of the plan.  
`rejected` | `bool` | `True` if the eligibility check was rejected for this plan.  
`reject_reason` | `str` or `None` | Reason for rejection, if applicable.  
`service_types` | `list[str]` | List of service types covered (e.g., "MEDICAL", "RX").  
####  Response Handler Example 
    ```python
    from canvas_sdk.events import EventType
    from canvas_sdk.events.surescripts import EligibilityPlan, SurescriptsEligibilityResponse
    from canvas_sdk.handlers.base import BaseHandler
    from logger import log
    class HandleEligibilityResponse(BaseHandler):
        RESPONDS_TO = [EventType.Name(EventType.SURESCRIPTS_ELIGIBILITY_RESPONSE)]
        def compute(self):
            # Parse the event context into a typed response object
            response = SurescriptsEligibilityResponse.from_context(self.event.context)
            log.info(f"Received eligibility response for correlation_id: {response.correlation_id}")
            log.info(f"Patient ID: {response.patient_id}")
            if response.error:
                log.error(f"Eligibility check failed: {response.error}")
                return []
            for plan in response.plans:
                if plan.rejected:
                    log.warning(f"Plan rejected: {plan.pbm_name} - {plan.reject_reason}")
                else:
                    log.info(f"Active plan: {plan.pbm_name}, Member ID: {plan.member_id}")
                    if plan.service_types:
                        log.info(f"  Service types: {', '.join(plan.service_types)}")
            return []
    ```
##  Benefits 
Retrieve formulary and coverage details for a specific medication. Send a request with `SendSurescriptsBenefitsRequestEffect`, then handle the `SURESCRIPTS_BENEFITS_RESPONSE` event when the response arrives.
###  SendSurescriptsBenefitsRequestEffect 
Sends a benefits request to Surescripts to retrieve formulary and coverage details for a specific medication. The response arrives as a `SURESCRIPTS_BENEFITS_RESPONSE` event.
####  Attributes 
Name | Type | Description  
---|---|---  
`patient_id` | `str` | The Canvas [Patient](/sdk/data-patient/#patient) ID for whom to check benefits.  
`staff_id` | `str` | The Canvas [Staff](/sdk/data-staff/#staff) ID initiating the request.  
`medication_description` | `str` | A human-readable description of the medication (e.g., "Lipitor 10 mg tablet").  
`medication_ndc` | `str` | The NDC of the medication to check.  
`plan` | `str` | The plan or PBM to check benefits against.  
`correlation_id` | `str` | A unique identifier for matching the response to this request. Auto-generated if not provided. Read this value after instantiation and store it for later use.  
####  Correlation ID 
As with eligibility requests, each benefits request includes a `correlation_id` that echoes back in the corresponding `SURESCRIPTS_BENEFITS_RESPONSE` event. Use this to match responses to their originating requests when handling multiple concurrent benefits checks.
By default, the effect auto-generates a unique `correlation_id` (a UUID hex string). You can pass your own value if you need to thread external state through the request-response cycle.
> **Note:** The `correlation_id` is required for receiving response events. The platform only delivers `SURESCRIPTS_BENEFITS_RESPONSE` events to plugins that sent a request with a valid `correlation_id`.
####  Example Usage 
    ```python
    from canvas_sdk.effects.surescripts.surescripts_messages import SendSurescriptsBenefitsRequestEffect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers.base import BaseHandler
    class CheckBenefitsOnPrescription(BaseHandler):
        RESPONDS_TO = [EventType.Name(EventType.PRESCRIBE_COMMAND__POST_ORIGINATE)]
        def compute(self):
            patient_id = self.event.target.get("id")
            staff_id = self.event.context.get("created_by", {}).get("id")
            effect = SendSurescriptsBenefitsRequestEffect(
                patient_id=patient_id,
                staff_id=staff_id,
                medication_description="Lipitor 10 mg tablet",
                medication_ndc="00071015523",
                plan="Acme PBM",
            )
            # Store the correlation_id to match the response later
            correlation_id = effect.correlation_id
            return [effect.apply()]
    ```
###  Handling Benefits Responses 
When Surescripts returns a benefits response, the platform fires a `SURESCRIPTS_BENEFITS_RESPONSE` event. Use the typed data classes from `canvas_sdk.events.surescripts` to parse the response.
> **Important:** To prevent infinite loops, you cannot return a `SendSurescriptsBenefitsRequestEffect` from a handler that responds to `SURESCRIPTS_BENEFITS_RESPONSE` events.
####  Response Data Classes 
#####  SurescriptsBenefitsResponse 
The top-level response object containing benefits results.
Name | Type | Description  
---|---|---  
`correlation_id` | `str` | The correlation ID from the originating request.  
`patient_id` | `str` | The Canvas [Patient](/sdk/data-patient/#patient) ID for this benefits check.  
`medication_ndc` | `str` | The NDC of the medication that was checked.  
`coverages` | BenefitCoverage[] | List of coverage results returned in the response.  
`error` | `str` or `None` | Error message if the request failed, otherwise `None`.  
#####  BenefitCoverage 
Represents a single coverage result from the benefits response.
Name | Type | Description  
---|---|---  
`pbm_name` | `str` | Name of the Pharmacy Benefit Manager.  
`payer_id` | `str` | Identifier for the insurance payer ([Transactor](/sdk/data-coverage/#transactor)).  
`formulary_status` | `str` or `None` | Formulary status of the medication (e.g., "On Formulary").  
`prior_authorization_required` | `bool` | `True` if prior authorization is required.  
`step_therapy_required` | `bool` | `True` if step therapy is required.  
`quantity_limits` | `list[str]` | Human-readable quantity limits (e.g., "30 fills per 1 calendar year").  
`copays` | `list[str]` | Human-readable copay descriptions (e.g., "Tier 2: $25.00").  
`alternatives` | TherapeuticAlternative[] | Therapeutic alternatives for the requested medication.  
`rejected` | `bool` | `True` if the benefits check was rejected for this coverage.  
`reject_reason` | `str` or `None` | Reason for rejection, if applicable.  
#####  TherapeuticAlternative 
Represents a therapeutic alternative suggested for the requested medication.
Name | Type | Description  
---|---|---  
`ndc` | `str` | The NDC of the alternative medication.  
`description` | `str` or `None` | Human-readable description of the alternative.  
`brand_or_generic` | `str` or `None` | Whether the alternative is "Brand" or "Generic".  
`rx_or_otc` | `str` or `None` | Whether the alternative is "Rx" or "OTC".  
`formulary_status` | `str` or `None` | Formulary status of the alternative.  
`prior_authorization_required` | `bool` | `True` if prior authorization is required.  
`step_therapy_required` | `bool` | `True` if step therapy is required.  
`quantity_limits` | `list[str]` | Human-readable quantity limits.  
`copays` | `list[str]` | Human-readable copay descriptions.  
####  Response Handler Example 
    ```python
    from canvas_sdk.events import EventType
    from canvas_sdk.events.surescripts import (
        BenefitCoverage,
        SurescriptsBenefitsResponse,
        TherapeuticAlternative,
    )
    from canvas_sdk.handlers.base import BaseHandler
    from logger import log
    class HandleBenefitsResponse(BaseHandler):
        RESPONDS_TO = [EventType.Name(EventType.SURESCRIPTS_BENEFITS_RESPONSE)]
        def compute(self):
            # Parse the event context into a typed response object
            response = SurescriptsBenefitsResponse.from_context(self.event.context)
            log.info(f"Received benefits response for correlation_id: {response.correlation_id}")
            log.info(f"Medication NDC: {response.medication_ndc}")
            if response.error:
                log.error(f"Benefits check failed: {response.error}")
                return []
            for coverage in response.coverages:
                if coverage.rejected:
                    log.warning(f"Coverage rejected: {coverage.pbm_name} - {coverage.reject_reason}")
                    continue
                log.info(f"{coverage.pbm_name} formulary status: {coverage.formulary_status}")
                if coverage.prior_authorization_required:
                    log.info("  Prior authorization required")
                for copay in coverage.copays:
                    log.info(f"  Copay: {copay}")
                for alternative in coverage.alternatives:
                    log.info(f"  Alternative: {alternative.description} ({alternative.ndc})")
            return []
    ```
##  Medication History 
Request a patient's medication history from Surescripts.
Unlike eligibility and benefits, this effect has **no paired response event** — Canvas retrieves the medication history and processes it on the platform side; the results are not delivered back to your plugin. There is no `correlation_id` and no `SURESCRIPTS_MEDICATION_HISTORY_RESPONSE` event to handle.
###  SendSurescriptsMedicationHistoryRequestEffect 
Sends a medication history request to Surescripts for the patient. Canvas requests the patient's recent fill history (currently the trailing 12 months).
####  Attributes 
Name | Type | Description  
---|---|---  
`patient_id` | `str` | The Canvas [Patient](/sdk/data-patient/#patient) ID whose medication history to request.  
`staff_id` | `str` | The Canvas [Staff](/sdk/data-staff/#staff) ID initiating the request.  
####  Example Usage 
    ```python
    from canvas_sdk.effects.surescripts.surescripts_messages import SendSurescriptsMedicationHistoryRequestEffect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers.base import BaseHandler
    class RequestMedicationHistoryOnAppointment(BaseHandler):
        RESPONDS_TO = [EventType.Name(EventType.APPOINTMENT_CREATED)]
        def compute(self):
            patient_id = self.event.target.get("id")
            staff_id = self.event.context.get("created_by", {}).get("id")
            return [
                SendSurescriptsMedicationHistoryRequestEffect(
                    patient_id=patient_id,
                    staff_id=staff_id,
                ).apply()
            ]
    ```
##  Imports 
    ```python
    # Effects for sending requests
    from canvas_sdk.effects.surescripts.surescripts_messages import (
        SendSurescriptsBenefitsRequestEffect,
        SendSurescriptsEligibilityRequestEffect,
        SendSurescriptsMedicationHistoryRequestEffect,
    )
    # Data classes for parsing responses
    from canvas_sdk.events.surescripts import (
        BenefitCoverage,
        EligibilityPlan,
        SurescriptsBenefitsResponse,
        SurescriptsEligibilityResponse,
        TherapeuticAlternative,
    )
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/effect-surescripts/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-task-metadata/
The `TaskMetadata` effect provides a flexible key-value storage system for task-specific data within the Canvas system. This effect enables the creation and updating of custom metadata entries associated with task records, allowing for extensible task information storage beyond standard task fields.
##  Overview 
Task metadata serves as a powerful extension mechanism for storing custom task-related information that doesn't fit within the standard task data model.
##  Attributes 
Attribute | Type | Description | Required  
---|---|---|---  
`task_id` | `str` | Id of the task record to associate metadata with | Yes  
`key` | `str` | Unique identifier for the metadata entry within the task context | Yes  
##  Methods 
###  upsert(value: str) → Effect 
Creates or updates a metadata entry for the specified task and key combination.
####  Parameters 
Parameter | Type | Description | Required  
---|---|---|---  
`value` | `str` | The metadata value to store | Yes  
####  Returns 
An `Effect` object configured for upserting task metadata.
####  Behavior 
  - If a metadata entry with the specified key already exists for the task, it will be updated with the new value
  - If no entry exists, a new metadata entry will be created
  - The operation is idempotent - repeated calls with the same key and value will not create duplicate entries
##  Implementation Details 
###  Validation 
The effect performs comprehensive validation before execution:
  1. **Task Existence Validation** : Verifies that the referenced task exists in the system
  - Queries the task database to confirm the `task_id` corresponds to an existing task record
  - Returns a descriptive error if the task is not found
  1. **Field Validation** : Ensures all required fields are provided and properly formatted
  - Both `task_id` and `key` must be non-empty strings
  - The `value` parameter in the `upsert` method must be provided
###  Data Structure 
The effect payload is structured as JSON with the following schema:
    ```json
    {
      "data": {
        "task_id": "task-id",
        "key": "metadata-key",
        "value": "metadata-value"
      }
    }
    ```
##  Example Usage 
###  Basic Usage 
    ```python
    from canvas_sdk.effects.task import TaskMetadata
    # Create a metadata entry for task tracking
    metadata = TaskMetadata(
        task_id="550e8400e29b41d4a716446655440000",
        key="external_system_id"
    )
    # Upsert the metadata value
    effect = metadata.upsert("EXT-12345")
    ```
###  Task Integration Example 
    ```python
    import json
    from canvas_sdk.effects.task import TaskMetadata
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.events import EventType
    class TaskMetadataHandler(BaseHandler):
      """
      Adds metadata to tasks based on task properties.
      """
      RESPONDS_TO = EventType.Name(EventType.TASK_CREATED)
      def compute(self):
        task_id = self.context["task"]["id"]
        task_labels = self.context.get("task", {}).get("labels", [])
        effects = []
        # Store task creation source
        metadata = TaskMetadata(
          task_id=task_id,
          key="creation_source"
        )
        effects.append(metadata.upsert("protocol"))
        # Store label information as JSON
        if task_labels:
          labels_metadata = TaskMetadata(
            task_id=task_id,
            key="original_labels"
          )
          effects.append(labels_metadata.upsert(json.dumps(task_labels)))
        return effects
    ```
##  Best Practices 
###  Key Naming Conventions 
  1. **Use Descriptive Names** : Choose keys that clearly indicate the purpose of the metadata
  - Good: `external_system_id`, `workflow_stage`, `integration_source`
  - Avoid: `data1`, `temp`, `misc`
  1. **Namespace Your Keys** : When building integrations or modules, prefix keys to avoid collisions
  - Example: `integration_task_id`, `workflow_current_stage`, `automation_trigger_id`
###  Value Storage 
  1. **String Serialization** : All values are stored as strings. For complex data types: 
         ```python
         # Storing JSON data
         import json
         from canvas_sdk.effects.task import TaskMetadata
         metadata = TaskMetadata(
             task_id="550e8400e29b41d4a716446655440000",
             key="workflow_state"
         )
         complex_data = {"stage": "review", "approvers": ["user1", "user2"], "timestamp": "2025-01-15T10:30:00Z"}
         metadata.upsert(json.dumps(complex_data))
         ```
  2. **Boolean Values** : Store as "true" or "false" strings for consistency 
         ```python
         from canvas_sdk.effects.task import TaskMetadata
         needs_followup = True
         metadata = TaskMetadata(
             task_id="550e8400e29b41d4a716446655440000",
             key="requires_followup"
         )
         metadata.upsert("true" if needs_followup else "false")
         ```
##  Notes 
  - Metadata entries are task-specific and isolated - the same key can have different values for different tasks
  - There is no built-in versioning; updating a key overwrites the previous value
  - The system does not enforce any schema on metadata values - validation is the responsibility of the implementing code
----- END PAGE https://docs.canvasmedical.com/sdk/effect-task-metadata/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effect-tasks/
The Canvas SDK includes functionality to create, update and add comments to tasks in Canvas.
##  Adding a Task 
To add a task, import the `AddTask` class and create an instance of it.
Attribute |  | Type | Description  
---|---|---|---  
id | optional | string or UUID | Task unique UUID. If none one will be generated automatically.  
assignee_id | optional | string | The id of the [staff](/sdk/data-staff/) the task should be assigned to.  
team_id | optional | string | The id of the [team](/sdk/data-team/) the task should be assigned to.  
patient_id | optional | string | The id of the [patient](/sdk/data-patient/) the task is associated with.  
title | required | string | The title of the task. This is displayed at the top of a task card in the Canvas UI.  
due | optional | datetime | A date/time when the task is due.  
status | optional | TaskStatus | A status of OPEN, CLOSED or COMPLETED. Defaults to OPEN if not supplied.  
priority | optional | TaskPriority | A priority of `STAT`, `URGENT`, or `ROUTINE`. Defaults to no priority if not supplied.  
labels | optional | list[string] | A list of labels that will be added at the bottom of a task card in the Canvas UI.  
author_id | optional | string or UUID | Author's id to set task creator, defaults to CanvasBot.  
linked_object_id | optional | string or UUID | Linked object id of linked object.  
linked_object_type | optional | LinkableObjectType | Type of the LinkedObject  
###  Enumeration Types 
####  Linked Object Type 
Value | Description  
---|---  
REFERRAL | REFERRAL  
IMAGING | IMAGING  
####  TaskPriority 
Value | Description  
---|---  
STAT | The request should be actioned immediately — highest possible priority. E.g. an emergency.  
URGENT | The request should be actioned promptly — higher priority than routine.  
ROUTINE | The request has normal priority.  
An example of adding a task:
    ```python
    import arrow
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.task import AddTask, AddTaskComment, UpdateTask, TaskPriority, TaskStatus
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.v1.data.lab import LabReport
    from canvas_sdk.v1.data.staff import Staff
    from canvas_sdk.v1.data.team import Team
    from canvas_sdk.v1.data.referral import Referral
    class MyHandler(BaseHandler):
        RESPONDS_TO = [
            EventType.Name(EventType.LAB_REPORT_CREATED),
        ]
        def compute(self) -> list[Effect]:
            lab_report = LabReport.objects.get(id=self.target)
            staff_assignee = Staff.objects.get(last_name="Weed")
            team = Team.objects.get(name="Labs")
            linked_task_type = AddTask.LinkableObjectType.REFERRAL
            referral = Referral.objects.get(id="d2194110-5c9a-4842-8733-ef09ea5ead11")
            if lab_report.patient:
                add_task = AddTask(
                    assignee_id=staff_assignee.id,
                    author_id=staff_assignee.id,
                    team_id = team.id,
                    patient_id=lab_report.patient.id,
                    title="Please call the patient with their test results.",
                    due=arrow.utcnow().shift(days=5).datetime,
                    status=TaskStatus.OPEN,
                    priority=TaskPriority.URGENT,
                    labels=["call"],
                    linked_object_id=referral.id,
                    linked_object_type=linked_task_type,
                )
                return [add_task.apply()]
            return []
    ```
##  Updating a Task 
To update an existing task, import the `UpdateTask` class and create an instance of it.
Attribute |  | Type | Description  
---|---|---|---  
id | required | string | The id of the task being updated.  
assignee_id | optional | string | The id of the [staff](/sdk/data-staff/) the task should be assigned to.  
team_id | optional | string | The id of the [team](/sdk/data-team/) the task should be assigned to.  
patient_id | optional | string | The id of the [patient](/sdk/data-patient/) the task is associated with.  
title | optional | string | The title of the task. This is displayed at the top of a task card in the Canvas UI.  
due | optional | datetime | A date/time when the task is due.  
status | optional | TaskStatus | A status of `OPEN`, `CLOSED` or `COMPLETED`. Defaults to `OPEN` if not supplied.  
priority | optional | TaskPriority | A priority of `STAT`, `URGENT`, or `ROUTINE`. See TaskPriority.  
labels | optional | list[string] | A list of labels that will be added at the bottom of a task card in the Canvas UI.  
An example of updating a task to a status of `COMPLETED`:
    ```python
    from canvas_sdk.effects.task import UpdateTask, TaskStatus
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        def compute(self):
            update_task = UpdateTask(
                id="d06276ba-85c5-471b-87c0-9c9805f4ca6f",
                status=TaskStatus.COMPLETED,
            )
            return [update_task.apply()]
    ```
##  Adding a comment to a task 
To add a comment to a task, import the `AddTaskComment` class and create an instance of it.
Attribute |  | Type | Description  
---|---|---|---  
task_id | required | string | The id of the task being updated.  
body | required | string | The comment body.  
author_id | optional | string or UUID | Author's id to set task comment creator, defaults to CanvasBot.  
    ```python
    from canvas_sdk.effects.task import AddTaskComment
    from canvas_sdk.handlers.base import BaseHandler
    from canvas_sdk.v1.data.staff import Staff
    class MyHandler(BaseHandler):
        def compute(self):
            author = Staff.objects.get(last_name="Weed")
            add_task_comment = AddTaskComment(
                task_id="d06276ba-85c5-471b-87c0-9c9805f4ca6f",
                body="I tried to call the patient but did not get an answer.",
                author_id=author.id
            )
            return [add_task_comment.apply()]
    ```
##  Creating a task and a comment together 
`AddTaskComment` requires the `task_id` of an existing task. To create a brand new task **and** add a comment to it in a single `compute()` return, supply your own `id` to `AddTask` and reuse that same value as the `task_id` on `AddTaskComment`. Because the `id` on `AddTask` is optional and is generated for you when omitted, the trick is simply to generate it yourself so you can reference it on the comment.
There is no need to create the task first and listen for a follow-up event — just return both effects from the same handler, with the `AddTask` effect before the `AddTaskComment` effect.
    ```python
    import uuid
    import arrow
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.task import AddTask, AddTaskComment, TaskStatus
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class MyHandler(BaseHandler):
        RESPONDS_TO = [
            EventType.Name(EventType.LAB_REPORT_CREATED),
        ]
        def compute(self) -> list[Effect]:
            # Generate the task id up front so the comment can reference it.
            task_id = str(uuid.uuid4())
            add_task = AddTask(
                id=task_id,
                title="Please call the patient with their test results.",
                due=arrow.utcnow().shift(days=1).datetime,
                status=TaskStatus.OPEN,
            )
            add_task_comment = AddTaskComment(
                task_id=task_id,
                body="Results flagged abnormal — follow up today.",
            )
            # Order matters: the task must be created before the comment.
            return [add_task.apply(), add_task_comment.apply()]
    ```
> **Note:** The effects are applied in the order they are returned, so the `AddTask` effect must come before the `AddTaskComment` effect that references it. Both effects must be returned from the same handler — don't split them across separate handlers or plugins, and don't defer either effect, since that breaks the ordering the comment relies on.
----- END PAGE https://docs.canvasmedical.com/sdk/effect-tasks/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/effects/
Effects are instructions that plugins can return in order to perform an action in the Canvas EMR. This makes it possible to define workflows that create commands, show notifications, modify search results, etc.
Effects have a `type` and a `payload`. The `type` determines the action that will be performed with the data provided in the `payload`.
##  Using Effects 
###  Basic Usage 
Effects are returned as a list from the `compute` method of a plugin that inherits from `BaseHandler`. For example:
    ```python
    import json
    from canvas_sdk.events import EventType
    from canvas_sdk.effects import Effect, EffectType
    from canvas_sdk.handlers.base import BaseHandler
    class MyHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.MEDICATION_STATEMENT__MEDICATION__POST_SEARCH)
        def compute(self):
            results = self.context.get("results")
            post_processed_results = []
            ## custom results-modifying code here
            ...
            return [
                Effect(
                    type=EffectType.AUTOCOMPLETE_SEARCH_RESULTS,
                    payload=json.dumps(post_processed_results),
                )
            ]
    ```
In the above example, the `Effect` object is constructed manually, with the `type` and `payload` set directly.
Some effects have helper classes that assist you by providing payload validation and constructing the effect object for you. The example below shows the [`PatientChartSummaryConfiguration`](/sdk/layout-effect/#patient-summary) class in use:
    ```python
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers.base import BaseHandler
    from canvas_sdk.effects.patient_chart_summary_configuration import PatientChartSummaryConfiguration
    class CustomChartLayout(BaseHandler):
        """
        This event handler rearranges the patient summary section and hides those
        not used by the installation's organization.
        """
        # This event fires when a patient's chart summary section is loading.
        RESPONDS_TO = EventType.Name(EventType.PATIENT_CHART_SUMMARY__SECTION_CONFIGURATION)
        def compute(self):
            layout = PatientChartSummaryConfiguration(sections=[
              PatientChartSummaryConfiguration.Section.SOCIAL_DETERMINANTS,
              PatientChartSummaryConfiguration.Section.ALLERGIES,
              PatientChartSummaryConfiguration.Section.VITALS,
              PatientChartSummaryConfiguration.Section.MEDICATIONS,
              PatientChartSummaryConfiguration.Section.CONDITIONS,
              PatientChartSummaryConfiguration.Section.IMMUNIZATIONS,
            ])
            return [layout.apply()]
    ```
###  Async Execution 
By default, effects returned from a plugin's `compute` method are executed inline as part of the request that triggered them. Any `Effect` can be opted into asynchronous execution by chaining `.set_async()` on it, in which case the platform will run the effect as an asynchronous task instead of inline. This is useful for effects that should run on a delay, that are tolerant of retries, or that you don't want to block the originating request.
    ```python
    from canvas_sdk.effects.claim import ClaimEffect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class MyHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.NOTE_STATE_CHANGE_EVENT_CREATED)
        def compute(self):
            claim_id = self.event.context["claim_id"]
            return [
                ClaimEffect(claim_id=claim_id)
                .add_comment("Reviewed by automation.")
                .set_async(delay_seconds=60, max_retries=3)
            ]
    ```
`set_async()` returns the same `Effect` so it can be chained directly off any effect-producing call (e.g. `ClaimEffect(...).add_comment(...)`, `Response(...).apply()`, or a manually-constructed `Effect(...)`).
####  Parameters 
Parameter | Type | Description | Required  
---|---|---|---  
`delay_seconds` | `int` | Number of seconds to wait before running the effect. Must be non-negative. `0` schedules the effect to run asynchronously as soon as possible. | No  
`max_retries` | `int` | Maximum number of retry attempts on failure. Must be non-negative. When omitted, the platform default is applied. Pass `0` to explicitly disable retries. | No  
Both parameters are keyword-only. Calling `.set_async()` with neither argument is a no-op and returns the effect unchanged.
####  Implementation Details 
  - Negative values or non-integer values for `delay_seconds` or `max_retries` raise `TypeError` / `ValueError`.
###  Disallowed Effect/Event Combinations 
Canvas prevents certain combinations of events and effects to avoid infinite loops that could occur when an effect triggers the same event that generated it. The following combinations are specifically disallowed:
Event Type | Disallowed Effect Types  
---|---  
`PATIENT_CHART__CONDITIONS` | `ADD_BANNER_ALERT`  
`ADD_OR_UPDATE_PROTOCOL_CARD`  
`PATIENT_CHART_SUMMARY__SECTION_CONFIGURATION` | `ADD_BANNER_ALERT`  
`ADD_OR_UPDATE_PROTOCOL_CARD`  
For example, if you have a plugin that responds to `PATIENT_CHART__CONDITIONS` events, you cannot return `ADD_BANNER_ALERT` or `ADD_OR_UPDATE_PROTOCOL_CARD` effects from that plugin, as this could create an infinite loop where the effect triggers another conditions event.
##  Effect Classes 
[ Appointment Labels Programmatically manage labels on appointments for categorization and automation. ](/sdk/effect-appointment-labels/) [ Appointment Metadata Interact with appointment metadata. ](/sdk/effect-appointment-metadata/) [ Appointment Metadata Create Form Effect for dynamically displaying forms when scheduling an appointment. ](/sdk/appointment-metadata-create-form-effect/) [ Appointments Create, update, and cancel patient appointments. ](/sdk/effect-notes/#appointment-effect) [ Application Notification Badge Display and update a notification badge count on an application icon. ](/sdk/effect-application-notification-badge/) [ Banner Alerts Contextual information in a patient's chart. ](/sdk/effect-banner-alerts/) [ Batch Originate Commands Efficiently insert multiple commands into a note in a single batch operation. ](/sdk/effect-batch-originate/) [ Billing Line Items Add, modify, or remove billing codes on a note. ](/sdk/effect-billing-line-items/) [ C-CDA Export Create a C-CDA (Consolidated Clinical Document Architecture) document for a patient. ](/sdk/effect-create-ccda-export/) [ Claims Manage labels, update line items, move to a queue, or post a payment. ](/sdk/effect-claims/) [ Command Metadata Attach custom key-value metadata to command records. ](/sdk/effect-command-metadata/) [ Configure Command Buttons Hide or disable command buttons in specific patient chart locations. ](/sdk/effect-configure-command-buttons/) [ Command Metadata Create form Adds additional fields to commands that are stored as command metadata. ](/sdk/command-metadata-create-form-effect/) [ Command Validation Validate commands and return structured error messages to users. ](/sdk/effect-command-validation/) [ Compound Medications Create or update compound medications. ](/sdk/effect-compound-medication/) [ Create Calendar Create a calendar for a provider. ](/sdk/calendar-create-effect/) [ Create Patient Preferred Pharmacies Create preferred pharmacies for a patient. ](/sdk/effect-create-patient-preferred-pharmacies/) [ Custom HTML and Django Templates Render custom HTML using Django templates. ](/sdk/layout-effect/#custom-html-and-django-templates) [ Data Integration Manage documents in the Data Integration queue. ](/sdk/effect-data-integration/) [ Default Homepage Set a provider's default homepage in Canvas. ](/sdk/default-homepage-effect/) [ Event Validation Error Effect for blocking event creation with a validation error message. ](/sdk/effect-event-validation-error/) [ External Events Create or update external clinical events from ADT feeds. ](/sdk/effect-external-event/) [ HTTP Request Have the platform issue an HTTP request on behalf of a plugin. ](/sdk/effect-http-request/) [ Lab Report Create, update, enter-in-error, and attach results to a lab report. ](/sdk/effect-lab-report/) [ Layout Effects Modify or interact with the layout in Canvas. ](/sdk/layout-effect/) [ Manage Calendar Events Manage calendar events. ](/sdk/calendar-event-management-effects/) [ Messages Interact with messages in Canvas. ](/sdk/effect-messages/) [ Note Footer Configuration Configure a note's footer — for example, hide Canvas's default state-transition buttons. ](/sdk/effect-note-footer-configuration/) [ Note Metadata Attach custom key-value metadata to note records. ](/sdk/effect-note-metadata/) [ Note Restrictions Control access to notes in real time — restrict editing, blur content, and show banners via plugin-driven effects. ](/sdk/effect-note-restrictions/) [ Notes Interact with notes in Canvas. ](/sdk/effect-notes/) [ Observations Create or update clinical observations. ](/sdk/effect-observation/) [ Patient Interact with patient data. ](/sdk/effect-patient/) [ Patient Chart Group Effect for grouping items on a patient chart section. ](/sdk/patient-chart-group-effect/) [ Patient Chart Summary Custom Section Serve content for a custom section in the patient chart summary. ](/sdk/patient-chart-summary-custom-section-effect/) [ Patient External ID Create a new external identifier for a patient. ](/sdk/effect-create-patient-external-identifier/) [ Patient Facility Address Create, update, or delete patient facility address associations. ](/sdk/effect-patient-facility-address/) [ Patient Group Interact with patient group data. ](/sdk/effect-patient-group/) [ Patient Metadata Interact with patient metadata. ](/sdk/effect-patient-metadata/) [ Patient Metadata Create Form Effect for dynamically displaying forms in the Patient profile. ](/sdk/patient-metadata-create-form-effect/) [ Patient Portal Customize your Patient Portal. ](/sdk/patient-portal/) [ Patient Timeline Configure a patient's timeline by excluding specific note types. ](/sdk/effect-patient-timeline/) [ Payment Processor Effects returned by custom payment processors. ](/sdk/payment-processor-effect/) [ Protocol Cards Calls to action in a patient's chart, commonly used for decision support intervention. ](/sdk/effect-protocol-cards/) [ Questionnaires Interact with questionnaires and interviews. ](/sdk/effect-questionnaires/) [ Redirect Navigate the Canvas frontend to an allowlisted URL, page, or application. ](/sdk/effect-redirect/) [ Reload Action Buttons Re-evaluate a note's or patient's action buttons in real time. ](/sdk/effect-reload-action-buttons/) [ Send Contact Verification Send an email or SMS verification to a patient contact point. ](/sdk/effect-send-contact-verification/) [ ServiceProvider Create, update, and soft-deactivate providers in a customer-managed provider directory. ](/sdk/effect-service-provider/) [ Staff External ID Create, update, or delete an external identifier for a staff member. ](/sdk/effect-staff-external-identifier/) [ Staff Metadata Interact with staff metadata. ](/sdk/effect-staff-metadata/) [ Surescripts Query insurance eligibility, medication history, and benefits through Surescripts. ](/sdk/effect-surescripts/) [ Task Metadata Interact with Task metadata. ](/sdk/effect-task-metadata/) [ Tasks Create or update tasks. ](/sdk/effect-tasks/) [ Commands The building blocks of many end-user workflows in Canvas, including nearly all clinical workflows for documentation. ](/sdk/commands/)
##  Effect Types 
The following effects are available to be applied in Canvas.
###  Banner Alerts & Protocol Cards 
Effect | Description  
---|---  
ADD_BANNER_ALERT | Can be used to [add a banner alert](/sdk/effect-banner-alerts/#adding-a-banner-alert) to a patient's chart.  
REMOVE_BANNER_ALERT | Can be used to [remove a banner alert](/sdk/effect-banner-alerts/#removing-a-banner-alert) from a patient's chart.  
ADD_OR_UPDATE_PROTOCOL_CARD | Can be used to generate a ProtocolCard in the Canvas UI. Use the [ProtocolCard](/sdk/effect-protocol-cards/) class in the effects module.  
###  Layout & Navigation 
Effect | Description  
---|---  
SHOW_PATIENT_CHART_SUMMARY_SECTIONS | Can be used to reorder or hide the summary sections in a patient chart. Check out [this effect class](/sdk/layout-effect/#patient-summary).  
PATIENT_CHART_SUMMARY__CUSTOM_SECTION | Can be used to serve content for a custom patient chart summary section. Check out [Patient Chart Summary Custom Section](/sdk/patient-chart-summary-custom-section-effect/).  
SHOW_PATIENT_PROFILE_SECTIONS | Can be used to reorder or hide sections in the patient profile. Check out [Layout Effects](/sdk/layout-effect/#patient-profile).  
SHOW_PANEL_SECTIONS | Can be used to reorder or hide sections in the side panel. Check out [Layout Effects](/sdk/layout-effect/#panel-configuration).  
SHOW_PATIENT_NOTE_HEADER_DROPDOWN_SECTIONS | Can be used to hide items in the note header triple dot button dropdown. Check out [this effect class](/sdk/layout-effect/#patient-note-header-dropdown-configuration).  
SHOW_PROVIDER_MENU_ITEMS | Can be used to hide items in the provider (hamburger) menu. Check out [Layout Effects](/sdk/layout-effect/#provider-menu-configuration).  
PATIENT_CHART__GROUP_ITEMS | Can be used to group items within a specific patient chart section. Check out [Patient Chart Group](/sdk/patient-chart-group-effect/).  
PATIENT_TIMELINE__CONFIGURATION | Can be used to configure the patient timeline display. Check out [Patient Timeline](/sdk/effect-patient-timeline/).  
HOMEPAGE_CONFIGURATION | Can be used to configure the homepage layout. Check out [Default Homepage](/sdk/default-homepage-effect/).  
SHOW_ACTION_BUTTON | Can be used to show an action button. Check out [Action Buttons](/sdk/handlers-action-buttons/) and [LaunchModalEffects](/sdk/layout-effect/#modals).  
RELOAD_ACTION_BUTTONS | Can be used to refresh a note's or patient's action buttons so they re-evaluate against the latest data. Check out [Reload Action Buttons](/sdk/effect-reload-action-buttons/).  
SHOW_APPLICATION | Can be used to show a custom application. Check out [Applications](/sdk/handlers-applications/) and [LaunchModalEffects](/sdk/layout-effect/#modals).  
SET_APPLICATION_NOTIFICATION_BADGE | Can be used to display or update a notification badge count on an application icon. Check out [Application Notification Badge](/sdk/effect-application-notification-badge/).  
REDIRECT_CONTEXT | Returned from a [`SSO__GET_POST_LOGIN_REDIRECT`](/sdk/events/) handler to override the URL the user lands on after SAML SSO login. See [SSO Capabilities](/sdk/sso/#redirect_context).  
REDIRECT | Navigate the browser to an allowlisted external URL, internal Canvas page, or application from any handler (e.g. after a note is signed). Check out [Redirect](/sdk/effect-redirect/).  
PATIENT_CHART__CONFIGURE_COMMAND_BUTTONS | Can be used to hide or disable command buttons in specific patient chart locations. Check out [Configure Command Buttons](/sdk/effect-configure-command-buttons/).  
###  Search Results 
Effect | Description  
---|---  
AUTOCOMPLETE_SEARCH_RESULTS | Can be used to modify search results by re-ordering or adding text annotations to individual result records. To see how you can put this to use, check out [this guide](/guides/customize-search-results/).  
PATIENT_PROFILE__ADD_PHARMACY__POST_SEARCH_RESULTS | Can be used to modify pharmacy results when adding pharmacies in the patient profile.  
###  Annotations 
Check out [this guide](/guides/improve-hcc-coding-accuracy/#adding-annotations-to-conditions-and-detected-issues) for examples of using annotation effects.
Effect | Description  
---|---  
ANNOTATE_CLAIM_CONDITION_RESULTS | Add annotations to conditions appearing in a claim's detail view.  
ANNOTATE_PATIENT_CHART_CONDITION_RESULTS | Add an annotation to a condition within the patient summary.  
ANNOTATE_PATIENT_CHART_DETECTED_ISSUE_RESULTS | Add an annotation to a detected issue within the patient summary.  
###  Billing Line Items 
Check out the [Billing Line Items](/sdk/effect-billing-line-items/) effect class documentation.
Effect | Description  
---|---  
ADD_BILLING_LINE_ITEM | Generate a Billing Line Item in a note footer.  
UPDATE_BILLING_LINE_ITEM | Update an existing Billing Line Item in a note footer.  
REMOVE_BILLING_LINE_ITEM | Remove a Billing Line Item from a note footer.  
###  Tasks 
Check out the [Task Effects](/sdk/effect-tasks/) and [Task Metadata](/sdk/effect-task-metadata/) documentation.
Effect | Description  
---|---  
CREATE_TASK | Create a task from a plugin.  
UPDATE_TASK | Update an existing task.  
CREATE_TASK_COMMENT | Add a comment to an existing task.  
UPSERT_TASK_METADATA | Add or update metadata on a task.  
###  Command Metadata & Validation 
Effect | Description  
---|---  
UPSERT_COMMAND_METADATA | Add or update metadata on a command. Check out [Command Metadata](/sdk/effect-command-metadata/).  
SET_COMMAND_CUSTOM_HTML | Set or clear custom HTML content on a staged command. Check out [set_custom_html](/sdk/commands/#set_custom_html).  
COMMAND_AVAILABLE_ACTIONS_RESULTS | Sort or filter command available actions. Check out [Command Actions](/sdk/commands/#command-actions).  
COMMAND_VALIDATION_ERRORS | Return validation errors for commands. Check out [Command Validation](/sdk/effect-command-validation/).  
EVENT_VALIDATION_ERROR | Return validation errors for events. Check out [Event Validation Error](/sdk/effect-event-validation-error/).  
BATCH_ORIGINATE_COMMANDS | Originate multiple commands in a note at once. Check out [Batch Originate](/sdk/effect-batch-originate/).  
COMMAND__FORM__CREATE_ADDITIONAL_FIELDS | Returns additional fields to be displayed on a command and stored as command metadata. Check out [Command Metadata Create Form](/sdk/command-metadata-create-form-effect/).  
###  Notes 
Check out the [Note Effects](/sdk/effect-notes/) documentation.
Effect | Description  
---|---  
CREATE_NOTE | Create a note.  
UPDATE_NOTE | Update a note.  
LOCK_NOTE | Lock a note.  
UNLOCK_NOTE | Unlock a note.  
SIGN_NOTE | Sign a note.  
CHECK_IN_NOTE | Check in a note.  
NO_SHOW_NOTE | Mark a note as no-show.  
DELETE_NOTE | Delete a note.  
UNDELETE_NOTE | Restore a deleted note.  
DISCHARGE_NOTE | Lock and discharge an inpatient note.  
FAX_NOTE | Fax a note to an external recipient.  
PUSH_NOTE_CHARGES | Push note charges for billing.  
UPSERT_NOTE_METADATA | Add or update metadata on a note.  
GENERATE_FULL_CHART_PDF | Generate a full chart PDF for a patient.  
NOTE_RESTRICTIONS | Communicate whether a note is restricted for the requesting user, whether its content should be blurred, or what banner message to display. See [Note Restrictions](/sdk/effect-note-restrictions/).  
NOTE_RESTRICTIONS_UPDATED | Signal that note restrictions have changed, triggering an immediate real-time permission refetch on all users currently viewing that note. See [Note Restrictions](/sdk/effect-note-restrictions/).  
NOTE_FOOTER__CONFIGURATION | Configure a note's footer — for example, hide Canvas's default state-transition buttons so a plugin can supply its own. See [Note Footer Configuration](/sdk/effect-note-footer-configuration/).  
###  Appointments 
Check out the [Appointment Effects](/sdk/effect-notes/#appointment-effect), [Appointment Labels](/sdk/effect-appointment-labels/), and [Appointment Metadata](/sdk/effect-appointment-metadata/) documentation.
Effect | Description  
---|---  
CREATE_APPOINTMENT | Create an appointment.  
UPDATE_APPOINTMENT | Update an appointment.  
RESCHEDULE_APPOINTMENT | Reschedule an appointment.  
CANCEL_APPOINTMENT | Cancel an appointment.  
REVERT_APPOINTMENT | Revert a cancelled, converted, or no-showed appointment back to the booked state.  
ADD_APPOINTMENT_LABEL | Add one or more labels to an appointment (max 3 total).  
REMOVE_APPOINTMENT_LABEL | Remove one or more labels from an appointment.  
UPSERT_APPOINTMENT_METADATA | Add or update metadata on an appointment.  
###  Appointment Scheduling Form 
Check out the [Appointment Metadata Create Form](/sdk/appointment-metadata-create-form-effect/) documentation.
Effect | Description  
---|---  
APPOINTMENT__FORM__PROVIDERS__PRE_SEARCH_RESULTS | Modify the list of providers before a search.  
APPOINTMENT__FORM__LOCATIONS__PRE_SEARCH_RESULTS | Modify the list of locations before a search.  
APPOINTMENT__FORM__VISIT_TYPES__PRE_SEARCH_RESULTS | Modify the list of visit types before a search.  
APPOINTMENT__FORM__DURATIONS__PRE_SEARCH_RESULTS | Modify the list of durations before a search.  
APPOINTMENT__FORM__REASON_FOR_VISIT__PRE_SEARCH_RESULTS | Modify the reason for visit field before a search.  
APPOINTMENT__FORM__PROVIDERS__POST_SEARCH_RESULTS | Modify the list of providers after a search.  
APPOINTMENT__FORM__LOCATIONS__POST_SEARCH_RESULTS | Modify the list of locations after a search.  
APPOINTMENT__FORM__VISIT_TYPES__POST_SEARCH_RESULTS | Modify the list of visit types after a search.  
APPOINTMENT__FORM__DURATIONS__POST_SEARCH_RESULTS | Modify the list of durations after a search.  
APPOINTMENT__FORM__REASON_FOR_VISIT__POST_SEARCH_RESULTS | Modify the reason for visit field after a search.  
APPOINTMENT__FORM__CREATE_ADDITIONAL_FIELDS | Show additional fields on the appointment scheduling form.  
APPOINTMENT__SLOTS__POST_SEARCH_RESULTS | Modify slot availability when scheduling an appointment.  
###  Schedule Events 
Check out the [Schedule Event Effects](/sdk/effect-notes/#scheduleevent-effect) documentation.
Effect | Description  
---|---  
CREATE_SCHEDULE_EVENT | Create a schedule event.  
UPDATE_SCHEDULE_EVENT | Update a schedule event.  
DELETE_SCHEDULE_EVENT | Delete a schedule event.  
RESCHEDULE_SCHEDULE_EVENT | Reschedule a schedule event.  
###  Calendar 
Check out the [Create Calendar](/sdk/calendar-create-effect/) and [Manage Calendar Events](/sdk/calendar-event-management-effects/) documentation.
Effect | Description  
---|---  
CALENDAR__CREATE | Create a calendar.  
CALENDAR__EVENT__CREATE | Create a calendar event.  
CALENDAR__EVENT__UPDATE | Update a calendar event.  
CALENDAR__EVENT__DELETE | Delete a calendar event.  
###  Patients 
Check out the [Patient Effects](/sdk/effect-patient/), [Patient Metadata](/sdk/effect-patient-metadata/), [Patient External ID](/sdk/effect-create-patient-external-identifier/), and [Patient Facility Address](/sdk/effect-patient-facility-address/) documentation.
Effect | Description  
---|---  
CREATE_PATIENT | Create a patient.  
UPDATE_PATIENT | Update a patient.  
UPDATE_USER | Update a user.  
PATIENT_METADATA__CREATE_ADDITIONAL_FIELDS | Show additional fields on the patient profile section.  
UPSERT_PATIENT_METADATA | Add or update metadata on a patient.  
CREATE_PATIENT_EXTERNAL_IDENTIFIER | Create an external identifier for a patient.  
CREATE_PATIENT_PREFERRED_PHARMACIES | Set preferred pharmacies for a patient.  
CREATE_PATIENT_FACILITY_ADDRESS | Create a facility address for a patient.  
UPDATE_PATIENT_FACILITY_ADDRESS | Update a facility address for a patient.  
DELETE_PATIENT_FACILITY_ADDRESS | Delete a facility address for a patient.  
###  Patient Groups 
Check out the [Patient Group](/sdk/effect-patient-group/) documentation.
Effect | Description  
---|---  
PATIENT_GROUP__ADD_MEMBER | Add a member to a patient group.  
PATIENT_GROUP__DEACTIVATE_MEMBER | Deactivate a member from a patient group.  
###  Staff 
Effect | Description  
---|---  
[UPSERT_STAFF_METADATA](/sdk/effect-staff-metadata/) | Insert or update a key/value metadata entry on a staff member.  
[DELETE_STAFF_METADATA](/sdk/effect-staff-metadata/) | Remove a key/value metadata entry from a staff member.  
[CREATE_STAFF_EXTERNAL_IDENTIFIER](/sdk/effect-staff-external-identifier/) | Create a new external identifier on a staff member.  
[UPDATE_STAFF_EXTERNAL_IDENTIFIER](/sdk/effect-staff-external-identifier/) | Update fields on an existing external identifier.  
[DELETE_STAFF_EXTERNAL_IDENTIFIER](/sdk/effect-staff-external-identifier/) | Delete an external identifier from a staff member.  
###  Messages 
Check out the [Message Effects](/sdk/effect-messages/) documentation.
Effect | Description  
---|---  
CREATE_MESSAGE | Create a message.  
SEND_MESSAGE | Send a message.  
CREATE_AND_SEND_MESSAGE | Create and send a message in one step.  
EDIT_MESSAGE | Edit a message.  
###  Observations 
Check out the [Observation Effects](/sdk/effect-observation/) documentation.
Effect | Description  
---|---  
CREATE_OBSERVATION | Create an observation.  
UPDATE_OBSERVATION | Update an observation.  
ENTER_IN_ERROR_OBSERVATION | Mark an observation as entered in error.  
###  Lab Reports 
Check out the [Lab Report Effects](/sdk/effect-lab-report/) documentation.
Effect | Description  
---|---  
CREATE_LAB_REPORT | Create a lab report decoupled from its results (no order, PDF, or values required).  
UPDATE_LAB_REPORT | Update lab report metadata, such as its name or effective date.  
ENTER_IN_ERROR_LAB_REPORT | Mark a lab report as entered in error.  
ATTACH_LAB_REPORT_RESULTS | Attach lab tests and values to an existing report (additive).  
###  Questionnaire 
Check out the [Questionnaire Effects](/sdk/effect-questionnaires/) documentation.
Effect | Description  
---|---  
CREATE_QUESTIONNAIRE_RESULT | Create a questionnaire result.  
###  Compound Medications 
Check out the [Compound Medication Effects](/sdk/effect-compound-medication/) documentation.
Effect | Description  
---|---  
CREATE_COMPOUND_MEDICATION | Create a compound medication.  
UPDATE_COMPOUND_MEDICATION | Update a compound medication.  
###  Service Providers 
Check out the [Service Provider Effects](/sdk/effect-service-provider/) documentation.
Effect | Description  
---|---  
CREATE_SERVICE_PROVIDER | Create a service provider, or update a matching one.  
UPDATE_SERVICE_PROVIDER | Update a service provider.  
DEACTIVATE_SERVICE_PROVIDER | Deactivate a service provider without deleting it.  
###  External Events 
Check out the [External Event Effects](/sdk/effect-external-event/) documentation.
Effect | Description  
---|---  
CREATE_EXTERNAL_EVENT | Create an external event.  
UPDATE_EXTERNAL_EVENT | Update an external event.  
###  CCDA 
Check out the [C-CDA Export](/sdk/effect-create-ccda-export/) documentation.
Effect | Description  
---|---  
CREATE_CCDA | Create a CCDA document.  
###  Claims 
Check out the [Claims Effects](/sdk/effect-claims/) documentation.
Effect | Description  
---|---  
POST_CLAIM_PAYMENT | Post a payment to a claim.  
MOVE_CLAIM_TO_QUEUE | Move a claim to a different queue.  
ADD_CLAIM_LABEL | Add a label to a claim.  
REMOVE_CLAIM_LABEL | Remove a label from a claim.  
ADD_CLAIM_COMMENT | Add a comment to a claim.  
ADD_CLAIM_BANNER_ALERT | Add a banner alert to a claim.  
REMOVE_CLAIM_BANNER_ALERT | Remove a banner alert from a claim.  
UPDATE_CLAIM_PROVIDER | Update the provider on a claim.  
UPSERT_CLAIM_METADATA | Add or update metadata on a claim.  
UPDATE_CLAIM_LINE_ITEM | Update a line item on a claim.  
###  Patient Portal 
Check out the [Patient Portal](/sdk/patient-portal/) documentation.
Effect | Description  
---|---  
PORTAL_WIDGET | Add widgets to the patient portal landing page.  
SHOW_PATIENT_PORTAL_MENU_ITEMS | Configure menu items in the patient portal. Check out [Patient Portal](/sdk/patient-portal/#configure-portal-menu-items).  
PATIENT_PORTAL__APPLICATION_CONFIGURATION | Configure the patient portal application.  
PATIENT_PORTAL__FORM_RESULT | Return form results in the patient portal.  
PATIENT_PORTAL__APPOINTMENT_SHOW_MEETING_LINK | Show the 'join' button on the telehealth appointment card.  
PATIENT_PORTAL__APPOINTMENT_IS_CANCELABLE | Show the 'cancel' button on the appointment card.  
PATIENT_PORTAL__APPOINTMENT_IS_RESCHEDULABLE | Show the 'reschedule' button on the appointment card.  
PATIENT_PORTAL__SEND_INVITE | Trigger a portal invitation for a user.  
PATIENT_PORTAL__SEND_CONTACT_VERIFICATION | Send an email or SMS verification to a patient contact point. Works for any patient contact point, not just the portal. Check out [Send Contact Verification](/sdk/effect-send-contact-verification/).  
PATIENT_PORTAL__APPOINTMENTS__SLOTS__POST_SEARCH_RESULTS | Modify slot availability in the patient portal appointment scheduler.  
PATIENT_PORTAL__APPOINTMENTS__FORM_APPOINTMENT_TYPES__PRE_SEARCH_RESULTS | Modify appointment types in the patient portal before a search.  
PATIENT_PORTAL__APPOINTMENTS__FORM_APPOINTMENT_TYPES__POST_SEARCH_RESULTS | Modify appointment types in the patient portal after a search.  
PATIENT_PORTAL__APPOINTMENTS__FORM_LOCATIONS__PRE_SEARCH_RESULTS | Modify locations in the patient portal before a search.  
PATIENT_PORTAL__APPOINTMENTS__FORM_LOCATIONS__POST_SEARCH_RESULTS | Modify locations in the patient portal after a search.  
PATIENT_PORTAL__APPOINTMENTS__FORM_PROVIDERS__PRE_SEARCH_RESULTS | Modify providers in the patient portal before a search.  
PATIENT_PORTAL__APPOINTMENTS__FORM_PROVIDERS__POST_SEARCH_RESULTS | Modify providers in the patient portal after a search.  
###  Simple API 
Check out the [HTTP](/sdk/handlers-simple-api-http/) and [WebSocket](/sdk/handlers-simple-api-websocket/) SimpleAPI documentation.
Effect | Description  
---|---  
SIMPLE_API_RESPONSE | Return a response from a SimpleAPI HTTP endpoint.  
SIMPLE_API_WEBSOCKET_BROADCAST | Broadcast a message to WebSocket connections.  
###  HTTP Requests 
Check out the [HTTP Request](/sdk/effect-http-request/) documentation.
Effect | Description  
---|---  
HTTP_REQUEST | Have the platform issue an HTTP request on behalf of a plugin. Most useful when chained with [`.set_async(...)`](/sdk/effect-http-request/#async-execution) so the platform's async runner handles delay, retries, and retry-on-status-code behavior.  
###  Revenue / Payment Processor 
Effect | Description  
---|---  
REVENUE__PAYMENT_PROCESSOR__METADATA | Advertises a custom payment processor to Canvas. Use the [PaymentProcessorMetadata](/sdk/payment-processor-effect/#paymentprocessormetadata) class in the effects module.  
REVENUE__PAYMENT_PROCESSOR__FORM | Returns the HTML form used to collect and tokenize card details. Use the [PaymentProcessorForm](/sdk/payment-processor-effect/#paymentprocessorform) class in the effects module.  
REVENUE__PAYMENT_PROCESSOR__CREDIT_CARD_TRANSACTION | Returns the result of charging a card. Use the [CardTransaction](/sdk/payment-processor-effect/#cardtransaction) class in the effects module.  
REVENUE__PAYMENT_PROCESSOR__PAYMENT_METHOD | Returns a patient's saved payment method. Use the [PaymentMethod](/sdk/payment-processor-effect/#paymentmethod) class in the effects module.  
REVENUE__PAYMENT_PROCESSOR__PAYMENT_METHOD__ADD_RESPONSE | Returns the result of adding a payment method. Use the [AddPaymentMethodResponse](/sdk/payment-processor-effect/#addpaymentmethodresponse) class in the effects module.  
REVENUE__PAYMENT_PROCESSOR__PAYMENT_METHOD__REMOVE_RESPONSE | Returns the result of removing a payment method. Use the [RemovePaymentMethodResponse](/sdk/payment-processor-effect/#removepaymentmethodresponse) class in the effects module.  
###  Surescripts 
Check out the [Surescripts Effects](/sdk/effect-surescripts/) documentation.
Effect | Description  
---|---  
SEND_SURESCRIPTS_ELIGIBILITY_REQUEST | Can be used to send a Surescripts eligibility request. See [Eligibility](/sdk/effect-surescripts/#eligibility).  
SEND_SURESCRIPTS_BENEFITS_REQUEST | Can be used to send a Surescripts benefits request. See [Benefits](/sdk/effect-surescripts/#benefits).  
SEND_SURESCRIPTS_MEDICATION_HISTORY_REQUEST | Can be used to send a Surescripts medication history request. See [Medication History](/sdk/effect-surescripts/#medication-history).  
###  Data Integration 
Check out the [Data Integration Effects](/sdk/effect-data-integration/) documentation.
Effect | Description  
---|---  
ASSIGN_DOCUMENT_REVIEWER | Assign a staff member or team as reviewer to a document in the Data Integration queue.  
CATEGORIZE_DOCUMENT | Categorize a document in the Data Integration queue into a specific document type.  
JUNK_DOCUMENT | Mark a document in the Data Integration queue as junk (spam).  
LINK_DOCUMENT_TO_PATIENT | Link a document in the Data Integration queue to a patient by patient id.  
REMOVE_DOCUMENT_FROM_PATIENT | Remove or unlink a document from a patient in the Data Integration queue.  
UPDATE_DOCUMENT_FIELDS | Prefill template field values on a document in the Data Integration queue (`PrefillDocumentFields` class).  
###  Commands 
Check out the [Commands documentation](/sdk/commands/) for full details.
Command effects follow a consistent naming pattern: `{ACTION}_{COMMAND_TYPE}_COMMAND`. The available actions are:
Action | Description  
---|---  
ORIGINATE | Create and open a new command in a note. Supports an optional `commit` flag to also commit the command in the same operation if the command is commit-able via SDK.  
EDIT | Modify field values on an existing command.  
DELETE | Remove an uncommitted command from a note.  
COMMIT | Finalize and save a command.  
ENTER_IN_ERROR | Mark a committed command as entered in error.  
SEND | Transmit a committed command to an external system (prescribe, refill, adjust prescription, lab orders only).  
REVIEW | Place a command into review status (prescribe, refill, adjust prescription only).  
DELEGATE | Delegate the order to someone else to complete (imaging order, refer only).  
SIGN | Sign the order (imaging order, refer only).  
The following command types support `ORIGINATE`, `EDIT`, `DELETE`, `COMMIT`, and `ENTER_IN_ERROR` actions unless noted otherwise:
Command Type | Effect Prefix | Notes  
---|---|---  
Adjust Prescription | `*_ADJUST_PRESCRIPTION_COMMAND` | No COMMIT. Supports SEND and REVIEW  
Allergy | `*_ALLERGY_COMMAND` |   
Assess | `*_ASSESS_COMMAND` |   
Change Medication | `*_CHANGE_MEDICATION_COMMAND` |   
Chart Section Review | `*_CHART_SECTION_REVIEW_COMMAND` | ORIGINATE only  
Close Goal | `*_CLOSE_GOAL_COMMAND` |   
Custom Command | `*_CUSTOM_COMMAND_COMMAND` | ORIGINATE, ENTER_IN_ERROR  
Diagnose | `*_DIAGNOSE_COMMAND` |   
Exam | `*_EXAM_COMMAND` |   
Family History | `*_FAMILY_HISTORY_COMMAND` |   
Follow Up | `*_FOLLOW_UP_COMMAND` |   
Goal | `*_GOAL_COMMAND` |   
HPI | `*_HPI_COMMAND` |   
Imaging Order | `*_IMAGING_ORDER_COMMAND` | No COMMIT or SEND. Supports DELEGATE and SIGN  
Imaging Review | `*_IMAGING_REVIEW_COMMAND` |   
Immunization Statement | `*_IMMUNIZATION_STATEMENT_COMMAND` |   
Immunize | `*_IMMUNIZE_COMMAND` |   
Instruct | `*_INSTRUCT_COMMAND` |   
Lab Order | `*_LAB_ORDER_COMMAND` | Also supports SEND  
Lab Review | `*_LAB_REVIEW_COMMAND` |   
Medical History | `*_MEDICAL_HISTORY_COMMAND` |   
Medication Statement | `*_MEDICATION_STATEMENT_COMMAND` |   
Perform | `*_PERFORM_COMMAND` |   
Plan | `*_PLAN_COMMAND` |   
POC Lab Test | `*_POC_LAB_TEST_COMMAND` |   
Prescribe | `*_PRESCRIBE_COMMAND` | No COMMIT. Supports SEND and REVIEW  
Questionnaire | `*_QUESTIONNAIRE_COMMAND` |   
Reason For Visit | `*_REASON_FOR_VISIT_COMMAND` | ORIGINATE, EDIT, DELETE only  
Refer | `*_REFER_COMMAND` | No COMMIT. Supports DELEGATE and SIGN  
Reference | `*_REFERENCE_COMMAND` | EDIT does not refresh the rendered table  
Referral Review | `*_REFERRAL_REVIEW_COMMAND` |   
Refill | `*_REFILL_COMMAND` | No COMMIT. Supports SEND and REVIEW  
Remove Allergy | `*_REMOVE_ALLERGY_COMMAND` |   
Resolve Condition | `*_RESOLVE_CONDITION_COMMAND` |   
Review of Systems | `*_ROS_COMMAND` |   
Stop Medication | `*_STOP_MEDICATION_COMMAND` |   
Structured Assessment | `*_STRUCTURED_ASSESSMENT_COMMAND` |   
Surgical History | `*_SURGICAL_HISTORY_COMMAND` |   
Task | `*_TASK_COMMAND` |   
Uncategorized Document Review | `*_UNCATEGORIZED_DOCUMENT_REVIEW_COMMAND` |   
Update Diagnosis | `*_UPDATE_DIAGNOSIS_COMMAND` |   
Update Goal | `*_UPDATE_GOAL_COMMAND` |   
Vitals | `*_VITALS_COMMAND` |   
----- END PAGE https://docs.canvasmedical.com/sdk/effects/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/event-health-gorilla-lab-order-prepared/
`HEALTH_GORILLA_LAB_ORDER_PREPARED` fires from Canvas right after the outbound Health Gorilla FHIR `RequestGroup` dict is constructed and right before it is POSTed to Health Gorilla. Plugin handlers receive the prepared dict via the event context and can store it, forward it to a partner backend, or surface it in the chart.
The event is **read-only** : it has no associated effect type, and any effects a handler returns are discarded. It does not affect the send path.
This complements [`LAB_ORDER_COMMAND__PRE_SEND`](/sdk/events/), which fires _before_ the build and lets a plugin inject overrides via [`HealthGorillaLabOrderOverride`](/sdk/effect-health-gorilla-lab-order-override/). The pair gives a plugin both an inject hook and a verify hook on every outbound HG order.
##  Event context 
The plugin handler receives `self.event.context` as a JSON-serialized dict with the following keys:
Key | Type | Description  
---|---|---  
lab_order | dict | `{"id": "<uuid>", "uuid": "<uuid>"}` — the LabOrder external id.  
lab_partner | str | Ontology lab partner name (`order.ontology_lab_partner`).  
patient | dict | `{"id": "<key>"}` if the order has a patient, else `{}`.  
note | dict | `{"id": "<uuid>", "uuid": "<uuid>"}` — only present when the order has a Note FK.  
request_group | dict | The full FHIR `RequestGroup` as it will be POSTed to HG, including all overrides applied by `LAB_ORDER_COMMAND__PRE_SEND` handlers.  
##  Example 
    ```python
    import json
    from canvas_generated.messages.events_pb2 import EventType
    from canvas_sdk.handlers.base import BaseHandler
    class LogHGPayload(BaseHandler):
        """Capture every outbound HG RequestGroup for partner verification."""
        RESPONDS_TO = EventType.Name(EventType.HEALTH_GORILLA_LAB_ORDER_PREPARED)
        def compute(self):
            context = json.loads(self.event.context)
            request_group = context["request_group"]
            lab_order_id = context["lab_order"]["id"]
            # store / forward / display the dict however you need
            return []  # no effect type associated with this event
    ```
##  Related 
  - [`LAB_ORDER_COMMAND__PRE_SEND`](/sdk/events/) — fires _before_ the build, accepts override effects
  - [`HealthGorillaLabOrderOverride`](/sdk/effect-health-gorilla-lab-order-override/) — the override effect returned from PRE_SEND
  - [HG `RequestGroup` profile](https://developer.healthgorilla.com/docs/requestgroup)
----- END PAGE https://docs.canvasmedical.com/sdk/event-health-gorilla-lab-order-prepared/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/events/
**What is an Event?**
An event is an occurrence of an action that happens within Canvas. For example, a patient being prescribed a medication, a user searching for a condition or an appointment being created are all examples of events.
**Why should I use them?**
By writing plugins that respond to events, plugin code is notified and can react to events that occur in Canvas. This enables plugin authors to create custom workflows whenever a relevant event takes place, such as making a POST request to a webhook.
**How do I use them?**
To make plugin code react to an event, you can add the event types listed below into the `RESPONDS_TO` list of a plugin that inherits from `BaseHandler`. For example:
    ```python
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class MyHandler(BaseHandler):
        RESPONDS_TO = [EventType.Name(EventType.ALLERGY_INTOLERANCE_CREATED)]
        def compute(self):
            ...
    ```
The plugin author can enter custom workflow code into the `compute` method that will execute every time an Allergy Intolerance is created in Canvas.
For more information on writing plugins, see the guide [here](/guides/your-first-plugin/).
##  Event Actor 
The actor is the user that initiated the event. It can be accessed within the compute method of the plugin by `self.event.actor`. For side-effect events or automated events where the action cannot be attributed to a specific user, the actor may be absent.
The actor is available in the following contexts:
  - [**SimpleAPI**](/sdk/handlers-simple-api/) handlers — HTTP and WebSocket requests
  - [**Action button**](/sdk/handlers-action-buttons/) handlers — button display and click events
  - [**Application**](/sdk/handlers-applications/) handlers
  - **Note state change events** — `NOTE_STATE_CHANGE_EVENT_PRE_CREATE`, `NOTE_STATE_CHANGE_EVENT_CREATED`, `NOTE_STATE_CHANGE_EVENT_UPDATED`
  - **Note UI events** — `NOTE_OPENED`, `NOTE_CLOSED`
  - **Note restrictions events** — `GET_NOTE_RESTRICTIONS`
  - **Note footer events** — `NOTE_FOOTER__GET_CONFIGURATION`
  - **Appointment scheduling events** — all `APPOINTMENT__*` events
  - **Patient chart and profile events** — all `PATIENT_CHART__*` events (conditions, medications, detected issues, etc.), chart summary configuration, panel sections, and patient metadata
  - **Patient timeline events** — `PATIENT_TIMELINE__GET_CONFIGURATION`
  - **Homepage events** — `GET_HOMEPAGE_CONFIGURATION`
  - **Command additional-fields events** — `COMMAND__FORM__GET_ADDITIONAL_FIELDS`
  - **Lab order command events** — `LAB_ORDER_COMMAND__PRE_SEND`, `HEALTH_GORILLA_LAB_ORDER_PREPARED`
  - **Claim events** — `CLAIM__CONDITIONS`
  - **SSO events** — `SSO__PROCESS_ADDITIONAL_REQUEST_DATA`, `SSO__GET_POST_LOGIN_REDIRECT`
  - **Payment processor events** — all `REVENUE__PAYMENT_PROCESSOR__*` events
  - **Patient portal events** — all `PATIENT_PORTAL__*` events
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.handlers import BaseHandler
    from logger import log
    class CustomHandler(BaseHandler):
        RESPONDS_TO = []
        def compute(self) -> list[Effect]:
            actor = self.event.actor
            log.info(actor.dbid)        # The database ID of the actor, if available
            log.info(actor.instance)  # The corresponding CanvasUser instance
            log.info(actor.instance.person_subclass) # The corresponding Staff or Patient instance
            return []
    ```
##  Event Types and Context 
The event `target` object can be accessed within the compute method of the plugin by `self.event.target`. If `self.event.target.type` exists, it provides the same type that would be imported from the Data module. For example, a type of `Condition` would be the same as what you can import from `canvas_sdk.v1.data.condition`.
The event `context` object can be accessed via `self.event.context`. The content present in each event's context depends on the event type. The table below shows what you can expect for each event type, or you could take a look yourself by logging it out.
###  Common Context Patterns 
Many events include common contextual information to help you understand the scope and origin of the event:
  - **Patient context** : Most patient-related events include `"patient": {"id": pt_id}` in the context, allowing you to identify which patient the event relates to.
  - **Note context** : Command lifecycle events (PRE_COMMIT, POST_COMMIT, etc.) include `"note": {"uuid": note_id}` in the context, indicating the note where the command was executed.
  - **User context** : All command-related PRE_SEARCH and POST_SEARCH events include `"user": {"staff": staff_key}` in the context, containing the staff key of the user performing the search. This allows you to customize search results based on user-specific preferences, roles, or permissions.
    ```python
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from logger import log
    class MyHandler(BaseHandler):
        RESPONDS_TO = [EventType.Name(EventType.ALLERGY_INTOLERANCE_CREATED)]
        def compute(self):
            log.info(self.event.context)
            return []
    ```
###  Record lifecycle events 
These events fire as a result of records being created, updated, or deleted.
####  Patients 
PATIENT_CREATED  
---  
Occurs when a patient is created.  
Target object | Context object  
    "id": pt_id
    "type": [Patient](/sdk/data-patient/)
| 
    empty  
PATIENT_UPDATED  
---  
Occurs when a patient's data is updated.  
Target object | Context object  
    "id": pt_id
    "type": [Patient](/sdk/data-patient/)
| 
    empty  
PATIENT_PREFERRED_PHARMACY_UPDATED  
---  
Occurs when a patient's preferred pharmacy is created or updated.  
Target object | Context object  
    "id": pt_id
    "type": [Patient](/sdk/data-patient/)
| 
    "patient":
        "id": pt_id  
CARE_TEAM_MEMBERSHIP_CREATED  
---  
Occurs when a new care team member is added for a patient.  
Target object | Context object  
    "id": care_team_membership_id
    "type": [CareTeamMembership](/sdk/data-care-team/#careteammembership)
| 
    "patient":
        "id": pt_id  
CARE_TEAM_MEMBERSHIP_UPDATED  
---  
Occurs when a care team member is adjusted for a patient.  
Target object | Context object  
    "id": care_team_membership_id
    "type": [CareTeamMembership](/sdk/data-care-team/#careteammembership)
| 
    "patient":
        "id": pt_id  
CARE_TEAM_MEMBERSHIP_DELETED  
---  
Occurs when a care team member is removed for a patient.  
Target object | Context object  
    "id": care_team_membership_id
    "type": [CareTeamMembership](/sdk/data-care-team/#careteammembership)
| 
    "patient":
        "id": pt_id  
PATIENT_ADDRESS_CREATED  
---  
Occurs when an address is added for a patient.  
Target object | Context object  
    "id": address_id
    "type": [PatientAddress](/sdk/data-patient/#patientaddress)
| 
    "patient":
        "id": pt_id  
PATIENT_ADDRESS_UPDATED  
---  
Occurs when one of a patient's addresses is updated.  
Target object | Context object  
    "id": address_id
    "type": [PatientAddress](/sdk/data-patient/#patientaddress)
| 
    "patient":
        "id": pt_id  
PATIENT_ADDRESS_DELETED  
---  
Occurs when one of a patient's addresses is removed.  
Target object | Context object  
    "id": address_id
    "type": [PatientAddress](/sdk/data-patient/#patientaddress)
| 
    "patient":
        "id": pt_id  
PATIENT_CONTACT_PERSON_CREATED  
---  
Occurs when a contact is added for a patient.  
Target object | Context object  
    "id": contact_person_id
    "type": None
| 
    "patient":
        "id": pt_id  
PATIENT_CONTACT_PERSON_UPDATED  
---  
Occurs when one of a patient's contacts is updated.  
Target object | Context object  
    "id": contact_person_id
    "type": None
| 
    "patient":
        "id": pt_id  
PATIENT_CONTACT_PERSON_DELETED  
---  
Occurs when one of a patient's contacts is removed.  
Target object | Context object  
    "id": contact_person_id
    "type": None
| 
    "patient":
        "id": pt_id  
PATIENT_CONTACT_POINT_CREATED  
---  
Occurs when a contact method for a patient is added.  
Target object | Context object  
    "id": contact_point_id
    "type": [PatientContactPoint](/sdk/data-patient/#patientcontactpoint)
| 
    "patient":
        "id": pt_id  
PATIENT_CONTACT_POINT_UPDATED  
---  
Occurs when a contact method for a patient is updated.  
Target object | Context object  
    "id": contact_point_id
    "type": [PatientContactPoint](/sdk/data-patient/#patientcontactpoint)
| 
    "patient":
        "id": pt_id  
PATIENT_CONTACT_POINT_DELETED  
---  
Occurs when a contact method for a patient is removed.  
Target object | Context object  
    "id": contact_point_id
    "type": [PatientContactPoint](/sdk/data-patient/#patientcontactpoint)
| 
    "patient":
        "id": pt_id  
PATIENT_EXTERNAL_IDENTIFIER_CREATED  
---  
Occurs when an external identifier is created for a patient.  
Target object | Context object  
    "id": patientexternalidentifier_id
    "type": [PatientExternalIdentifier](/sdk/data-patient/#patientexternalidentifier)
| 
    "patient":
        "id": pt_id  
PATIENT_EXTERNAL_IDENTIFIER_UPDATED  
---  
Occurs when an external identifier for a patient is updated.  
Target object | Context object  
    "id": patientexternalidentifier_id
    "type": [PatientExternalIdentifier](/sdk/data-patient/#patientexternalidentifier)
| 
    "patient":
        "id": pt_id  
PATIENT_EXTERNAL_IDENTIFIER_DELETED  
---  
Occurs when an external identifier for a patient is deleted.  
Target object | Context object  
    "id": patientexternalidentifier_id
    "type": [PatientExternalIdentifier](/sdk/data-patient/#patientexternalidentifier)
| 
    "patient":
        "id": pt_id  
####  Patient Facility Address 
PATIENT_FACILITY_ADDRESS_CREATED  
---  
Occurs when a patient facility address is created.  
Target object | Context object  
    "id": patientfacilityaddress_id
    "type": [PatientFacilityAddress](/sdk/data-patient/#patientfacilityaddress)
| 
    "patient":
        "id": pt_id  
PATIENT_FACILITY_ADDRESS_UPDATED  
---  
Occurs when a patient facility address is updated.  
Target object | Context object  
    "id": patientfacilityaddress_id
    "type": [PatientFacilityAddress](/sdk/data-patient/#patientfacilityaddress)
| 
    "patient":
        "id": pt_id  
PATIENT_FACILITY_ADDRESS_DELETED  
---  
Occurs when a patient facility address is deleted.  
Target object | Context object  
    "id": patientfacilityaddress_id
    "type": [PatientFacilityAddress](/sdk/data-patient/#patientfacilityaddress)
| 
    "patient":
        "id": pt_id  
####  Patient Metadata 
PATIENT_METADATA_CREATED  
---  
Occurs when a patient's metadata is created.  
Target object | Context object  
    "id": patientmetadata_id
    "type": [PatientMetadata](/sdk/data-patient/#patientmetadata)
| 
    "patient":
        "id": pt_id  
PATIENT_METADATA_UPDATED  
---  
Occurs when a patient's metadata is updated.  
Target object | Context object  
    "id": patientmetadata_id
    "type": [PatientMetadata](/sdk/data-patient/#patientmetadata)
| 
    "patient":
        "id": pt_id  
####  Allergy Intolerances 
ALLERGY_INTOLERANCE_CREATED  
---  
Occurs when an allergy is created for a patient. Additional details for the allergy may become available with subsequent ALLERGY_INTOLERANCE_UPDATED events.  
Target object | Context object  
    "id": allergy_id
    "type": [AllergyIntolerance](/sdk/data-allergy-intolerance/#allergyintolerance)
| 
    "patient":
        "id": pt_id  
ALLERGY_INTOLERANCE_UPDATED  
---  
Occurs when an allergy is updated for a patient.  
Target object | Context object  
    "id": allergy_id
    "type": [AllergyIntolerance](/sdk/data-allergy-intolerance/#allergyintolerance)
| 
    "patient":
        "id": pt_id  
####  Appointments 
APPOINTMENT_CREATED  
---  
Occurs when an appointment is first created/booked.  
Target object | Context object  
    "id": appointment_id
    "type": [Appointment](/sdk/data-appointment/#appointment)
| 
    "patient":
        "id": pt_id  
APPOINTMENT_UPDATED  
---  
Occurs when details of an appointment are updated.  
Target object | Context object  
    "id": appointment_id
    "type": [Appointment](/sdk/data-appointment/#appointment)
| 
    "patient":
        "id": pt_id  
APPOINTMENT_CHECKED_IN  
---  
Occurs when a patient has arrived and been checked in for their appointment.  
Target object | Context object  
    "id": appointment_id
    "type": [Appointment](/sdk/data-appointment/#appointment)
| 
    "patient":
        "id": pt_id  
APPOINTMENT_RESTORED  
---  
Occurs when a cancelled appointment is restored to a non-cancelled status.  
Target object | Context object  
    "id": appointment_id
    "type": [Appointment](/sdk/data-appointment/#appointment)
| 
    "patient":
        "id": pt_id  
APPOINTMENT_CANCELED  
---  
Occurs when an appointment is cancelled.  
Target object | Context object  
    "id": appointment_id
    "type": [Appointment](/sdk/data-appointment/#appointment)
| 
    "patient":
        "id": pt_id  
APPOINTMENT_NO_SHOWED  
---  
Occurs when an appointment is marked as a no-show.  
Target object | Context object  
    "id": appointment_id
    "type": [Appointment](/sdk/data-appointment/#appointment)
| 
    "patient":
        "id": pt_id  
APPOINTMENT_LABEL_ADDED  
---  
Occurs when one or more labels are added to an appointment.  
Target object | Context object  
    "id": appointment_id
    "type": None
| 
    "patient":
        "id": pt_id
    "label": label_name  
APPOINTMENT_LABEL_REMOVED  
---  
Occurs when one or more labels are removed from an appointment.  
Target object | Context object  
    "id": appointment_id
    "type": None
| 
    "patient":
        "id": pt_id
    "label": label_name  
APPOINTMENT__SLOTS__POST_SEARCH  
---  
Occurs when requesting slot availability when scheduling an appointment.  
Target object | Context object  
| 
    "slots_by_provider": list[dict]
    "selected_values": dict  
APPOINTMENT__FORM__PROVIDERS__PRE_SEARCH  
---  
Occurs when a schedule appointment form is loaded.  
Target object | Context object  
    "id": appointment_id
    "type": [Appointment](/sdk/data-appointment/#appointment)
| 
    "category": [NoteTypeCategories](/sdk/data-note/#notetypecategories)  
APPOINTMENT__FORM__PROVIDERS__POST_SEARCH  
---  
Occurs when a schedule appointment form is loaded.  
Target object | Context object  
    "id": appointment_id
    "type": [Appointment](/sdk/data-appointment/#appointment)
| 
    "providers": list[dict]
    "selected_values": dict
    "category": [NoteTypeCategories](/sdk/data-note/#notetypecategories)  
APPOINTMENT__FORM__LOCATIONS__PRE_SEARCH  
---  
Occurs when a schedule appointment form is loaded.  
Target object | Context object  
    "id": appointment_id
    "type": [Appointment](/sdk/data-appointment/#appointment)
| 
    "category": [NoteTypeCategories](/sdk/data-note/#notetypecategories)  
APPOINTMENT__FORM__LOCATIONS__POST_SEARCH  
---  
Occurs when a schedule appointment form is loaded.  
Target object | Context object  
    "id": appointment_id
    "type": [Appointment](/sdk/data-appointment/#appointment)
| 
    "locations": list[dict]
    "selected_values": dict
    "category": [NoteTypeCategories](/sdk/data-note/#notetypecategories)  
APPOINTMENT__FORM__VISIT_TYPES__PRE_SEARCH  
---  
Occurs when a schedule appointment form is loaded.  
Target object | Context object  
    "id": appointment_id
    "type": [Appointment](/sdk/data-appointment/#appointment)
| 
    "category": [NoteTypeCategories](/sdk/data-note/#notetypecategories)  
APPOINTMENT__FORM__VISIT_TYPES__POST_SEARCH  
---  
Occurs when a schedule appointment form is loaded.  
Target object | Context object  
    "id": appointment_id
    "type": [Appointment](/sdk/data-appointment/#appointment)
| 
    "visit_types": list[dict]
    "selected_values": dict
    "category": [NoteTypeCategories](/sdk/data-note/#notetypecategories)  
APPOINTMENT__FORM__DURATIONS__PRE_SEARCH  
---  
Occurs when a schedule appointment form is loaded.  
Target object | Context object  
    "id": appointment_id
    "type": [Appointment](/sdk/data-appointment/#appointment)
| 
    "category": [NoteTypeCategories](/sdk/data-note/#notetypecategories)  
APPOINTMENT__FORM__DURATIONS__POST_SEARCH  
---  
Occurs when a schedule appointment form is loaded.  
Target object | Context object  
    "id": appointment_id
    "type": [Appointment](/sdk/data-appointment/#appointment)
| 
    "durations": list[dict]
    "selected_values": dict
    "category": [NoteTypeCategories](/sdk/data-note/#notetypecategories)  
APPOINTMENT__FORM__REASON_FOR_VISIT__PRE_SEARCH  
---  
Occurs when a schedule appointment form is loaded.  
Target object | Context object  
    "id": appointment_id
    "type": [Appointment](/sdk/data-appointment/#appointment)
| 
    "category": [NoteTypeCategories](/sdk/data-note/#notetypecategories)  
APPOINTMENT__FORM__REASON_FOR_VISIT__POST_SEARCH  
---  
Occurs when a schedule appointment form is loaded.  
Target object | Context object  
    "id": appointment_id
    "type": [Appointment](/sdk/data-appointment/#appointment)
| 
    "reason_for_visit": list[dict]
    "selected_values": dict
    "category": [NoteTypeCategories](/sdk/data-note/#notetypecategories)  
APPOINTMENT__FORM__GET_ADDITIONAL_FIELDS  
---  
Occurs when a schedule appointment form is loaded.  
Target object | Context object  
    "id": appointment_id
    "type": [Appointment](/sdk/data-appointment/#appointment)
| 
    "category": [NoteTypeCategories](/sdk/data-note/#notetypecategories)  
APPOINTMENT__FORM__UPDATED  
---  
Occurs when a schedule appointment form is updated.  
Target object | Context object  
    "id": appointment_id
    "type": [Appointment](/sdk/data-appointment/#appointment)
| 
    "patient_id": int
    "selected_values": dict
    "category": [NoteTypeCategories](/sdk/data-note/#notetypecategories)  
####  Command Metadata 
COMMAND_METADATA_CREATED  
---  
Occurs when metadata is created on a command.  
Target object | Context object  
    "id": commandmetadata_id
    "type": [CommandMetadata](/sdk/data-command/#commandmetadata)
| 
    empty  
COMMAND_METADATA_UPDATED  
---  
Occurs when metadata on a command is updated.  
Target object | Context object  
    "id": commandmetadata_id
    "type": [CommandMetadata](/sdk/data-command/#commandmetadata)
| 
    empty  
####  Appointment Metadata 
APPOINTMENT_METADATA_CREATED  
---  
Occurs when an appointment's metadata is created.  
Target object | Context object  
    "id": appointmentmetadata_id
    "type": [AppointmentMetadata](/sdk/data-appointment/#appointmentmetadata)
| 
    "appointment":
        "id": appointment_id
    "patient":  # present only when the appointment has a patient
        "id": pt_id  
APPOINTMENT_METADATA_UPDATED  
---  
Occurs when an appointment's metadata is updated.  
Target object | Context object  
    "id": appointmentmetadata_id
    "type": [AppointmentMetadata](/sdk/data-appointment/#appointmentmetadata)
| 
    "appointment":
        "id": appointment_id
    "patient":  # present only when the appointment has a patient
        "id": pt_id  
####  Claims 
CLAIM_CREATED  
---  
Occurs when a claim is created.  
Target object | Context object  
    "id": claim_id
    "type": [Claim](/sdk/data-claim/#claim)
| 
    "patient":
      "id": pt_id
    "note":
      "uuid": note_id  
CLAIM_UPDATED  
---  
Occurs when a claim is updated.  
Target object | Context object  
    "id": claim_id
    "type": [Claim](/sdk/data-claim/#claim)
| 
    "patient":
      "id": pt_id
    "note":
      "uuid": note_id  
CLAIM_QUEUE_MOVED  
---  
Occurs when a claim moves from one queue to another.  
Target object | Context object  
    "id": claim_id
    "type": [Claim](/sdk/data-claim/#claim)
| 
    "patient":
      "id": pt_id
    "note":
      "id": note_id
    "queue_entered":
      "id": queue_id
    "queue_exited":
      "id": queue_id  
CLAIM__CONDITIONS  
---  
Fires when the conditions list is loaded inside the claim summary view. Plugins can use this event to surface plugin-specific diagnosis information alongside the existing diagnosis codes on the claim.  
Target object | Context object  
    "id": claim_id
    "type": [Claim](/sdk/data-claim/#claim)
| 
    [
      "id": str,
      "codings": [
        "code": str,
        "system": str,
        "display": str
      ]
    ]  
CLAIM_SUPERVISING_PROVIDER_CHANGED  
---  
Occurs when a claim's supervising provider snapshot is created or updated. The context includes the previous value(s) of any changed fields.  
Target object | Context object  
    "id": claim_id
    "type": [Claim](/sdk/data-claim/#claim)
| 
    "previous": null | {
      "first_name": str,
      "last_name": str,
      ...
    }  
CLAIM_INCIDENT_TO_CHANGED  
---  
Occurs when a claim's `incident_to` billing flag is changed. The context includes the previous boolean value.  
Target object | Context object  
    "id": claim_id
    "type": [Claim](/sdk/data-claim/#claim)
| 
    "previous": bool  
####  Billing Line Items 
BILLING_LINE_ITEM_CREATED  
---  
Occurs when a billing line item is created from adding a CPT code to a note.  
Target object | Context object  
    "id": billing_line_item_id
    "type": [BillingLineItem](/sdk/data-billing-line-item/#billinglineitem)
| 
    "patient":
        "id": pt_id  
BILLING_LINE_ITEM_UPDATED  
---  
Occurs when a billing line item is modified.  
Target object | Context object  
    "id": billing_line_item_id
    "type": [BillingLineItem](/sdk/data-billing-line-item/#billinglineitem)
| 
    "patient":
        "id": pt_id  
####  Calendars 
CALENDAR_CREATED  
---  
Occurs when a calendar is created.  
Target object | Context object  
    "id": calendar_id
    "type": [Calendar](/sdk/data-calendar/#calendar)
| 
    empty  
CALENDAR_UPDATED  
---  
Occurs when a calendar is updated.  
Target object | Context object  
    "id": calendar_id
    "type": [Calendar](/sdk/data-calendar/#calendar)
| 
    empty  
CALENDAR_DELETED  
---  
Occurs when a calendar is deleted.  
Target object | Context object  
    "id": calendar_id
    "type": [Calendar](/sdk/data-calendar/#calendar)
| 
    empty  
####  Calendar Events 
CALENDAR_EVENT_CREATED  
---  
Occurs when a calendar event is created.  
Target object | Context object  
    "id": event_id
    "type": [Event](/sdk/data-calendar/#event)
| 
    empty  
CALENDAR_EVENT_UPDATED  
---  
Occurs when a calendar event is updated.  
Target object | Context object  
    "id": event_id
    "type": [Event](/sdk/data-calendar/#event)
| 
    empty  
CALENDAR_EVENT_DELETED  
---  
Occurs when a calendar event is deleted.  
Target object | Context object  
    "id": event_id
    "type": [Event](/sdk/data-calendar/#event)
| 
    empty  
####  Patient Payments 
PATIENT_PAYMENT_PROCESSED  
---  
Occurs when a patient payment is processed in Canvas.  
Target object | Context object  
    "id": pt_id
    "type": [Patient](/sdk/data-patient/)
| 
    "patient_id": str
    "total_amount_cents": str
    "timestamp": str
    "payment_method_and_description": str
    "claim_payments": [
        {
            "claim_id": str,
            "allocated_cents": str
        }
    ]  
####  Clinical Documents 
These events fire during the lifecycle of documents in the [Data Integration](https://canvas-medical.help.usepylon.com/articles/4617508394-data-integration) module — including inbound faxes, uploaded documents, and electronic transmissions. Each event's context includes document metadata from the underlying [IntegrationTask](/sdk/data-integration-task/).
DOCUMENT_RECEIVED  
---  
Occurs when a new clinical document is received via fax, upload, or electronic transmission.  
Target object | Context object  
    "id": document_id
    "type": [IntegrationTask](/sdk/data-integration-task/)
| 
    "document":
      "id": document_id
      "channel": str
      "status": str
      "title": str
      "type": str
      "content_url": str
      "content_type": str
      "created_at": datetime str
    "patient":
      "id": pt_id
    "available_document_types":
        "key": str
        "name": str
        "report_type": str
        "template_type": str
        "template_fields":
            "name": str
            "label": str
            "type": str
            "required": bool  
DOCUMENT_LINKED_TO_PATIENT  
---  
Occurs when a clinical document is linked to a patient.  
Target object | Context object  
    "id": document_id
    "type": [IntegrationTask](/sdk/data-integration-task/)
| 
    "document":
      "id": document_id
      "channel": str
      "status": str
      "title": str
      "type": str
      "content_url": str
      "content_type": str
      "created_at": datetime str
    "patient":
      "id": pt_id
    "previous_patient":
      "id": pt_id
    "linked_at": datetime str
    "available_document_types":
        "key": str
        "name": str
        "report_type": str
        "template_type": str
        "template_fields":
            "name": str
            "label": str
            "type": str
            "required": bool  
DOCUMENT_CATEGORIZED  
---  
Occurs when a clinical document is categorized.  
Target object | Context object  
    "id": document_id
    "type": [IntegrationTask](/sdk/data-integration-task/)
| 
    "document":
      "id": document_id
      "channel": str
      "status": str
      "title": str
      "type": str
      "content_url": str
      "content_type": str
      "created_at": datetime str
    "document_type":
      "key": str
      "name": str
      "report_type": str
      "template_type": str
    "previous_document_type":
      "key": str
      "name": str
      "report_type": str
      "template_type": str
    "categorized_at": datetime str
    "patient":
      "id": pt_id  
DOCUMENT_REVIEWER_ASSIGNED  
---  
Occurs when a reviewer (Staff or Team) is assigned or reassigned on the Data Integration document review panel. This does not fire when a reviewer is assigned on a LabReport or ImagingReport — only when the [IntegrationTaskReview](/sdk/data-integration-task/) reviewer changes.  
Target object | Context object  
    "id": document_id
    "type": [IntegrationTask](/sdk/data-integration-task/)
| 
    "document":
      "id": document_id
      "channel": str
      "status": str
      "title": str
      "type": str
      "content_url": str
      "content_type": str
      "created_at": datetime str
    "assigned_at": datetime str
    "reviewer":
      "type": str
      "id": reviewer_id
      "name": str
    "previous_reviewer":
      "type": str
      "id": reviewer_id
      "name": str
    "patient":
      "id": pt_id  
DOCUMENT_REVIEWED  
---  
Occurs when a clinical document is marked as reviewed. This fires when the Data Integration task status changes to reviewed, or when a Lab Results Review, Imaging Report Review, Consult Report Review, or Uncategorized Document Review command is committed.  
Target object | Context object  
    "id": document_id
    "type": [IntegrationTask](/sdk/data-integration-task/)
| 
    "document":
      "id": document_id
      "channel": str
      "status": str
      "title": str
      "type": str
      "content_url": str
      "content_type": str
      "created_at": datetime str
    "review":
      "reviewer":
        "type": str
        "id": reviewer_id
        "name": str
      "status": str
      "patient_communication_method": str
      "internal_comment": str
      "message_to_patient": str
    "reviewed_at": datetime str
    "document_type":
      "key": str
      "name": str
      "report_type": str
      "template_type": str
    "patient":
      "id": pt_id  
DOCUMENT_DELETED  
---  
Occurs when a document is junked/deleted from the Data Integration panel. This does not fire when a report is junked from the patient chart.  
Target object | Context object  
    "id": document_id
    "type": [IntegrationTask](/sdk/data-integration-task/)
| 
    "document":
      "id": document_id
      "channel": str
      "status": str
      "title": str
      "type": str
      "content_url": str
      "content_type": str
      "created_at": datetime str
    "deleted_at": datetime str
    "patient":
      "id": pt_id
    "document_type":
      "key": str
      "name": str
      "report_type": str
      "template_type": str
    "deleted_by":
      "id": user_id
      "name": str  
DOCUMENT_FIELDS_UPDATED  
---  
Occurs when a clinical document's fields are updated. This fires when a Lab Report, Imaging Report, or Specialist Consult Report is parsed and its values are saved. The `updated_fields` list contains each changed field with its new and previous values.  
Target object | Context object  
    "id": document_id
    "type": [IntegrationTask](/sdk/data-integration-task/)
| 
    "document":
      "id": document_id
      "channel": str
      "status": str
      "title": str
      "type": str
      "content_url": str
      "content_type": str
      "created_at": datetime str
    "patient":
      "id": pt_id
    "updated_fields":
        "name": str
        "value": str | int | float | bool
        "previous_value": str | int | float | bool | None
    "document_type":
      "key": str
      "name": str
      "report_type": str
      "template_type": str
    "updated_at": datetime str  
####  Document Review Delegation 
The `DOCUMENT_DELEGATED` event fires when an uncategorized clinical document's review is delegated to another staff member or team, or routed back to its owner. It is a review-workflow event, separate from the Data Integration document-lifecycle events above.
DOCUMENT_DELEGATED  
---  
Occurs when an uncategorized clinical document review is delegated to another staff member or team from the document review surface, or routed back to its owner. This is distinct from DOCUMENT_REVIEWER_ASSIGNED, which fires only for Data Integration reviewer changes. `signature_consent` indicates whether the recipient may apply the owner's signature; `routed_back` is true when the document was returned to its owner; `comment` carries the delegator's instructions.  
Target object | Context object  
    "id": document_id
    "type": [IntegrationTask](/sdk/data-integration-task/)
| 
    "document": [UncategorizedClinicalDocument](/sdk/data-uncategorized-clinical-document/)
      "id": document_id
      "channel": str
      "status": str
      "title": str
      "type": str
      "content_url": str
      "content_type": str
      "created_at": datetime str
    "delegated_at": datetime str
    "delegated_by": [Staff](/sdk/data-staff/#staff)
      "type": str ("STAFF")
      "id": staff_id
      "name": str
    "delegated_to": [Staff](/sdk/data-staff/#staff) or [Team](/sdk/data-team/#team)
      "type": str ("STAFF" or "TEAM")
      "id": staff_or_team_id
      "name": str
    "on_behalf_of": [Staff](/sdk/data-staff/#staff)
      "type": str ("STAFF")
      "id": staff_id
      "name": str
    "signature_consent": bool
    "routed_back": bool
    "comment": str
    "patient": [Patient](/sdk/data-patient/#patient)
      "id": pt_id  
####  Conditions 
CONDITION_ASSESSED  
---  
Occurs when a condition is assessed through the Assess Condition command.  
Target object | Context object  
    "id": condition_id
    "type": [Condition](/sdk/data-condition/#condition)
| 
    "patient":
        "id": pt_id  
CONDITION_CREATED  
---  
Occurs when a condition is diagnosed for a patient. Additional details for the condition may become available with subsequent CONDITION_UPDATED events.  
Target object | Context object  
    "id": condition_id
    "type": [Condition](/sdk/data-condition/#condition)
| 
    "patient":
        "id": pt_id  
CONDITION_RESOLVED  
---  
Occurs when a condition is resolved through the Resolve Condition command.  
Target object | Context object  
    "id": condition_id
    "type": [Condition](/sdk/data-condition/#condition)
| 
    "patient":
        "id": pt_id  
CONDITION_UPDATED  
---  
Occurs when a condition is updated for a patient.  
Target object | Context object  
    "id": condition_id
    "type": [Condition](/sdk/data-condition/#condition)
| 
    "patient":
        "id": pt_id  
####  Consents 
CONSENT_CREATED  
---  
Occurs when a patient consent is created.  
Target object | Context object  
    "id": consent_id
    "type": None
| 
    "patient":
       "id": pt_id  
CONSENT_DELETED  
---  
Occurs when a patient consent is removed/deleted.  
Target object | Context object  
    "id": consent_id
    "type": None
| 
    "patient":
       "id": pt_id  
CONSENT_UPDATED  
---  
Occurs when a patient consent is updated.  
Target object | Context object  
    "id": consent_id
    "type": None
| 
    "patient":
       "id": pt_id  
####  Coverages 
COVERAGE_CREATED  
---  
Occurs when a coverage for a patient is created.  
Target object | Context object  
    "id": coverage_id
    "type": [Coverage](/sdk/data-coverage/#coverage)
| 
    "patient":
       "id": pt_id  
COVERAGE_UPDATED  
---  
Occurs when a coverage for a patient is updated.  
Target object | Context object  
    "id": coverage_id
    "type": [Coverage](/sdk/data-coverage/#coverage)
| 
    "patient":
       "id": pt_id  
####  Eligibility responses 
A `COVERAGE_ELIGIBILITY_RESPONSE_CREATED` or `COVERAGE_ELIGIBILITY_RESPONSE_UPDATED` event fires on every eligibility response save. When the response resolves to a definite status, a matching `COVERAGE_ELIGIBILITY_RESPONSE_ACTIVE`, `COVERAGE_ELIGIBILITY_RESPONSE_INACTIVE`, or `COVERAGE_ELIGIBILITY_RESPONSE_FAILED` event fires alongside it. For example, when a failed eligibility check is first recorded, both `COVERAGE_ELIGIBILITY_RESPONSE_CREATED` and `COVERAGE_ELIGIBILITY_RESPONSE_FAILED` fire. Each event's context carries the derived `status` string and the associated `coverage`; `_FAILED` events also include the payer `errors`.
COVERAGE_ELIGIBILITY_RESPONSE_CREATED  
---  
Occurs when an eligibility response is created for a coverage.  
Target object | Context object  
    "id": eligibility_response_id
    "type": [EligibilityResponse](/sdk/data-eligibility-response/#eligibilityresponse)
| 
    "coverage":
      "id": coverage_id
    "patient":
      "id": pt_id
    "status": str  
COVERAGE_ELIGIBILITY_RESPONSE_UPDATED  
---  
Occurs when an eligibility response is updated.  
Target object | Context object  
    "id": eligibility_response_id
    "type": [EligibilityResponse](/sdk/data-eligibility-response/#eligibilityresponse)
| 
    "coverage":
      "id": coverage_id
    "patient":
      "id": pt_id
    "status": str  
COVERAGE_ELIGIBILITY_RESPONSE_ACTIVE  
---  
Occurs when an eligibility response resolves to an active status.  
Target object | Context object  
    "id": eligibility_response_id
    "type": [EligibilityResponse](/sdk/data-eligibility-response/#eligibilityresponse)
| 
    "coverage":
      "id": coverage_id
    "patient":
      "id": pt_id
    "status": str  
COVERAGE_ELIGIBILITY_RESPONSE_INACTIVE  
---  
Occurs when an eligibility response resolves to an inactive status.  
Target object | Context object  
    "id": eligibility_response_id
    "type": [EligibilityResponse](/sdk/data-eligibility-response/#eligibilityresponse)
| 
    "coverage":
      "id": coverage_id
    "patient":
      "id": pt_id
    "status": str  
COVERAGE_ELIGIBILITY_RESPONSE_FAILED  
---  
Occurs when an eligibility response check fails to complete (the payer response errored).  
Target object | Context object  
    "id": eligibility_response_id
    "type": [EligibilityResponse](/sdk/data-eligibility-response/#eligibilityresponse)
| 
    "coverage":
      "id": coverage_id
    "patient":
      "id": pt_id
    "status": str
    "errors": list[str]  
####  Detected Issues 
DETECTED_ISSUE_CREATED  
---  
Occurs when a detected issue is created.  
Target object | Context object  
    "id": detected_issue_id
    "type": [DetectedIssue](/sdk/data-detected-issue/#detectedissue)
| 
    "patient":
       "id": pt_id  
DETECTED_ISSUE_UPDATED  
---  
Occurs when a detected issue is updated.  
Target object | Context object  
    "id": detected_issue_id
    "type": [DetectedIssue](/sdk/data-detected-issue/#detectedissue)
| 
    "patient":
       "id": pt_id  
DETECTED_ISSUE_EVIDENCE_CREATED  
---  
Occurs when detected issue evidence is created.  
Target object | Context object  
    "id": detected_issue_evidence_id
    "type": [DetectedIssueEvidence](/sdk/data-detected-issue/#detectedissueevidence)
| 
    empty  
DETECTED_ISSUE_EVIDENCE_UPDATED  
---  
Occurs when a detected issue evidence is updated.  
Target object | Context object  
    "id": detected_issue_evidence_id
    "type": [DetectedIssueEvidence](/sdk/data-detected-issue/#detectedissueevidence)
| 
    empty  
####  Devices 
DEVICE_CREATED  
---  
Occurs when a device is created.  
Target object | Context object  
    "id": device_id
    "type": [Device](/sdk/data-device/#device)
| 
    "patient":
       "id": pt_id  
DEVICE_UPDATED  
---  
Occurs when a device is updated.  
Target object | Context object  
    "id": device_id
    "type": [Device](/sdk/data-device/#device)
| 
    "patient":
       "id": pt_id  
####  Document References 
DOCUMENT_REFERENCE_CREATED  
---  
Occurs when a document reference is created.  
Target object | Context object  
    "id": document_reference_id
    "type": None
| 
    "patient":
       "id": pt_id  
DOCUMENT_REFERENCE_UPDATED  
---  
Occurs when a document reference is updated.  
Target object | Context object  
    "id": document_reference_id
    "type": None
| 
    "patient":
       "id": pt_id  
DOCUMENT_REFERENCE_DELETED  
---  
Occurs when a document reference is deleted.  
Target object | Context object  
    "id": document_reference_id
    "type": None
| 
    "patient":
       "id": pt_id  
####  Encounters 
ENCOUNTER_CREATED  
---  
Occurs when an encounter is created.  
Target object | Context object  
    "id": encounter_id
    "type": [Encounter](/sdk/data-encounter/#encounter)
| 
    empty  
ENCOUNTER_UPDATED  
---  
Occurs when an encounter is updated.  
Target object | Context object  
    "id": encounter_id
    "type": [Encounter](/sdk/data-encounter/#encounter)
| 
    empty  
####  Imaging Reports 
IMAGING_REPORT_CREATED  
---  
Occurs when an imaging report is entered into the data integration section of canvas.  
Target object | Context object  
    "id": report_id
    "type": [ImagingReport](/sdk/data-imaging/#imagingreport)
| 
    "patient":
       "id": pt_id  
IMAGING_REPORT_UPDATED  
---  
Occurs when an imaging report is updated.  
Target object | Context object  
    "id": report_id
    "type": [ImagingReport](/sdk/data-imaging/#imagingreport)
| 
    "patient":
       "id": pt_id  
####  Immunizations 
IMMUNIZATION_CREATED  
---  
Occurs when an immunization is created. Additional details for the immunization may become available with subsequent IMMUNIZATION_STATEMENT_UPDATED events.  
Target object | Context object  
    "id": immunization_id
    "type": [Immunization](/sdk/data-immunization/#immunization)
| 
    "patient":
       "id": pt_id  
IMMUNIZATION_UPDATED  
---  
Occurs when an immunization is updated.  
Target object | Context object  
    "id": immunization_id
    "type": [Immunization](/sdk/data-immunization/#immunization)
| 
    "patient":
       "id": pt_id  
IMMUNIZATION_STATEMENT_CREATED  
---  
Occurs when an immunization statement is created. Additional details for the immunization statement may become available with subsequent IMMUNIZATION_STATEMENT_UPDATED events.  
Target object | Context object  
    "id": immunization_id
    "type": [Immunization](/sdk/data-immunization/#immunization)
| 
    "patient":
       "id": pt_id  
IMMUNIZATION_STATEMENT_UPDATED  
---  
Occurs when an immunization statement is updated.  
Target object | Context object  
    "id": immunization_id
    "type": [Immunization](/sdk/data-immunization/#immunization)
| 
    "patient":
       "id": pt_id  
####  Instructions 
INSTRUCTION_CREATED  
---  
Occurs when an instruction is created using the Instruct command. Additional details for the instruction may become available with subsequent INSTRUCTION_UPDATED events.  
Target object | Context object  
    "id": instruction_id
    "type": None
| 
    "patient":
       "id": pt_id  
INSTRUCTION_UPDATED  
---  
Occurs when an instruction is updated.  
Target object | Context object  
    "id": instruction_id
    "type": None
| 
    "patient":
       "id": pt_id  
####  Interviews 
INTERVIEW_CREATED  
---  
Occurs when an interview is created using the Questionnaire command or through the Questionnaire endpoint in the FHIR API. Additional details for the interview may become available with subsequent INTERVIEW_UPDATED events.  
Target object | Context object  
    "id": interview_id
    "type": [Interview](/sdk/data-questionnaire/#interview)
| 
    "patient":
       "id": pt_id  
INTERVIEW_UPDATED  
---  
Occurs when an interview is updated.  
Target object | Context object  
    "id": interview_id
    "type": [Interview](/sdk/data-questionnaire/#interview)
| 
    "patient":
       "id": pt_id  
####  Labs 
LAB_ORDER_CREATED  
---  
Occurs when a lab order is created via the Lab Order command. Additional details for the lab order may become available with subsequent LAB_ORDER_UPDATED events.  
Target object | Context object  
    "id": laborder_id
    "type": [LabOrder](/sdk/data-labs/#laborder)
| 
    "patient":
       "id": pt_id  
LAB_ORDER_UPDATED  
---  
Occurs when a lab order is updated.  
Target object | Context object  
    "id": laborder_id
    "type": [LabOrder](/sdk/data-labs/#laborder)
| 
    "patient":
       "id": pt_id  
LAB_REPORT_CREATED  
---  
Occurs when a lab report is created either through Data Integration, electronic ingestion or the FHIR API.  
Target object | Context object  
    "id": labreport_id
    "type": [LabReport](/sdk/data-labs/#labreport)
| 
    "patient":
       "id": pt_id  
LAB_REPORT_UPDATED  
---  
Occurs when a lab report is updated.  
Target object | Context object  
    "id": labreport_id
    "type": [LabReport](/sdk/data-labs/#labreport)
| 
    "patient":
       "id": pt_id  
####  Medications 
MEDICATION_LIST_ITEM_CREATED  
---  
Occurs when a medication is added for a patient.  
Target object | Context object  
    "id": medication_id
    "type": [Medication](/sdk/data-medication/#medication)
| 
    "patient":
       "id": pt_id  
MEDICATION_LIST_ITEM_UPDATED  
---  
Occurs when a medication is updated for a patient.  
Target object | Context object  
    "id": medication_id
    "type": [Medication](/sdk/data-medication/#medication)
| 
    "patient":
       "id": pt_id  
PRESCRIPTION_UPDATED  
---  
Occurs when a prescription is updated.  
Target object | Context object  
    "id": prescription_id
    "type": [Prescription](/sdk/data-prescription)
| 
    "patient":
       "id": pt_id  
PRESCRIPTION_CREATED  
---  
Occurs when a prescription is created for a patient using the Prescribe command. Additional details for the prescription become available with subsequent PRESCRIPTION_UPDATED events.  
Target object | Context object  
    "id": prescription_id
    "type": [Prescription](/sdk/data-prescription)
| 
    "patient":
       "id": pt_id  
#####  Prescription status events 
The following events fire when a prescription's status changes during the e-prescribing lifecycle. These events always fire alongside a `PRESCRIPTION_CREATED` or `PRESCRIPTION_UPDATED` event. For example, when a prescription is first created, both `PRESCRIPTION_CREATED` and `PRESCRIPTION_OPENED` will fire. When a prescription's status is updated to "transmitted", both `PRESCRIPTION_UPDATED` and `PRESCRIPTION_TRANSMITTED` will fire.
PRESCRIPTION_OPENED  
---  
Occurs when a prescription's status is set to open. This is the default status when a prescription is first created.  
Target object | Context object  
    "id": prescription_id
    "type": [Prescription](/sdk/data-prescription)
| 
    "patient":
       "id": pt_id  
PRESCRIPTION_PENDING  
---  
Occurs when a prescription's status changes to pending.  
Target object | Context object  
    "id": prescription_id
    "type": [Prescription](/sdk/data-prescription)
| 
    "patient":
       "id": pt_id  
PRESCRIPTION_ACCEPTED  
---  
Occurs when a prescription has been ultimately accepted.  
Target object | Context object  
    "id": prescription_id
    "type": [Prescription](/sdk/data-prescription)
| 
    "patient":
       "id": pt_id  
PRESCRIPTION_ERRORED  
---  
Occurs when an error occurs during prescription processing.  
Target object | Context object  
    "id": prescription_id
    "type": [Prescription](/sdk/data-prescription)
| 
    "patient":
       "id": pt_id  
PRESCRIPTION_CANCEL_REQUESTED  
---  
Occurs when a cancellation has been requested for a prescription.  
Target object | Context object  
    "id": prescription_id
    "type": [Prescription](/sdk/data-prescription)
| 
    "patient":
       "id": pt_id  
PRESCRIPTION_CANCELED  
---  
Occurs when a prescription has been successfully canceled.  
Target object | Context object  
    "id": prescription_id
    "type": [Prescription](/sdk/data-prescription)
| 
    "patient":
       "id": pt_id  
PRESCRIPTION_CANCEL_DENIED  
---  
Occurs when a cancellation request for a prescription has been denied.  
Target object | Context object  
    "id": prescription_id
    "type": [Prescription](/sdk/data-prescription)
| 
    "patient":
       "id": pt_id  
PRESCRIPTION_RECEIVED  
---  
Occurs when a prescription has been received by the e-prescribing network.  
Target object | Context object  
    "id": prescription_id
    "type": [Prescription](/sdk/data-prescription)
| 
    "patient":
       "id": pt_id  
PRESCRIPTION_SIGNED  
---  
Occurs when a prescription has been signed.  
Target object | Context object  
    "id": prescription_id
    "type": [Prescription](/sdk/data-prescription)
| 
    "patient":
       "id": pt_id  
PRESCRIPTION_INQUEUE  
---  
Occurs when a prescription is in queue for transmission.  
Target object | Context object  
    "id": prescription_id
    "type": [Prescription](/sdk/data-prescription)
| 
    "patient":
       "id": pt_id  
PRESCRIPTION_TRANSMITTED  
---  
Occurs when a prescription has been transmitted to the pharmacy.  
Target object | Context object  
    "id": prescription_id
    "type": [Prescription](/sdk/data-prescription)
| 
    "patient":
       "id": pt_id  
PRESCRIPTION_DELIVERED  
---  
Occurs when a prescription has been delivered to the pharmacy.  
Target object | Context object  
    "id": prescription_id
    "type": [Prescription](/sdk/data-prescription)
| 
    "patient":
       "id": pt_id  
####  Surescripts 
Surescripts response events fire when the platform receives a response from Surescripts after a corresponding request effect is executed. These events let plugins react to insurance eligibility checks and other Surescripts services.
SURESCRIPTS_ELIGIBILITY_RESPONSE  
---  
Occurs when Surescripts returns an eligibility response after a `SendSurescriptsEligibilityRequestEffect` is executed. The response contains the patient's insurance plan information and coverage details. See [Handling Eligibility Responses](/sdk/effect-surescripts/#handling-eligibility-responses) for the typed response data classes and an example handler.  
Target object | Context object  
    empty
| 
    "correlation_id": str
    "patient_id": str
    "plans": list[dict]
    "error": str or None  
SURESCRIPTS_BENEFITS_RESPONSE  
---  
Occurs when Surescripts returns a benefits response after a `SendSurescriptsBenefitsRequestEffect` is executed. The response contains formulary and coverage details for the requested medication, including copays, quantity limits, and therapeutic alternatives. See [Handling Benefits Responses](/sdk/effect-surescripts/#handling-benefits-responses) for the typed response data classes and an example handler.  
Target object | Context object  
    empty
| 
    "correlation_id": str
    "patient_id": str
    "medication_ndc": str
    "coverages": list[dict]
    "error": str or None  
####  Messaging 
MESSAGE_CREATED  
---  
Occurs when a message (patient/practitioner communication) is created.  
Target object | Context object  
    "id": message_id
    "type": [Message](/sdk/data-message/#message)
| 
    "patient":
       "id": pt_id  
MESSAGE_TRANSMISSION_CREATED  
---  
Occurs when a message transmission record is created. Message transmissions track delivery attempts and status for messages sent through various channels (SMS, email, etc.).  
Target object | Context object  
    "id": message_transmission_id
    "type": [MessageTransmission](/sdk/data-message/#messagetransmission)
| 
    empty  
MESSAGE_TRANSMISSION_UPDATED  
---  
Occurs when a message transmission record is updated (e.g., when delivery status changes).  
Target object | Context object  
    "id": message_transmission_id
    "type": [MessageTransmission](/sdk/data-message/#messagetransmission)
| 
    empty  
####  Notes 
NOTE_STATE_CHANGE_EVENT_CREATED  
---  
Occurs as a note traverses through its state machine. This event can be used when looking at any changes to the [note state](/sdk/data-note/#notestates), including locking and unlocking.  
Target object | Context object  
    "id": nsce_id
    "type": NoteStateChangeEvent
| 
    "note_id": note_id,
    "patient_id": pt_id,
    "state": [str](/sdk/data-note/#notestates)  
NOTE_STATE_CHANGE_EVENT_PRE_CREATE  
---  
Occurs **before** a note state change event is created. This event allows protocols to perform validation and block the note state change if needed. If an [`EventValidationError`](/sdk/effect-event-validation-error) effect is returned, the note state change event is aborted and the error message is surfaced to the user.  
Target object | Context object  
    "id": nsce_id
    "type": NoteStateChangeEvent
| 
    "note_id": note_id,
    "patient_id": pt_id,
    "state": [str](/sdk/data-note/#notestates)  
NOTE_STATE_CHANGE_EVENT_UPDATED  
---  
Occurs if a note state change event is updated. Locking and unlocking both trigger an update event, and there is an *additional* update event when an archived PDF copy of the note finishes generating; this is done asynchronously.  
Target object | Context object  
    "id": nsce_id
    "type": NoteStateChangeEvent
| 
    "note_id": note_id,
    "patient_id": pt_id,
    "state": [str](/sdk/data-note/#notestates)  
NOTE_CREATED  
---  
Occurs when a note is created.  
Target object | Context object  
    "id": note_id
    "type": [Note](/sdk/data-note/)
| 
    "patient":
        "id": pt_id  
NOTE_UPDATED  
---  
Occurs when a note is updated, including changes to fields, commands, or body content.  
Target object | Context object  
    "id": note_id
    "type": [Note](/sdk/data-note/)
| 
    "patient":
        "id": pt_id
    "user":
        "id": staff_key  
NOTE_OPENED  
---  
Fires when a provider expands a note in the patient chart. The context includes the note's ID.  
Target object | Context object  
    "id": patient_key
    "type": Patient
| 
    "note": {"id": note_uuid},
    "user": {
      "type": str,
      "id": user_id
    }  
NOTE_CLOSED  
---  
Fires when a provider collapses a note that was previously open. The context includes the note's ID.  
Target object | Context object  
    "id": patient_key
    "type": Patient
| 
    "note": {"id": note_uuid},
    "user": {
      "type": str,
      "id": user_id
    }  
NOTE_SUPERVISING_PROVIDER_CHANGED  
---  
Occurs when a note's supervising provider is changed. The context includes the previous supervising provider's Staff ID, or `null` if the note previously had no supervising provider.  
Target object | Context object  
    "id": note_id
    "type": [Note](/sdk/data-note/)
| 
    "previous": null | {
      "id": staff_key
    }  
GET_NOTE_RESTRICTIONS  
---  
Fires every time a note is opened or its restrictions are refetched. Plugins respond with a [`NoteRestrictionsEffect`](/sdk/effect-note-restrictions/) to control whether the user can edit the note, whether the content is blurred, and what banner message is displayed. If no plugin responds, the note is unrestricted by default.  
Target object | Context object  
    "id": note.id
    "type": [Note](/sdk/data-note/)
| 
    empty  
####  Letters 
LETTER_CREATED  
---  
Occurs when a letter is created.  
Target object | Context object  
    "id": letter_id
    "type": [Letter](/sdk/data-letter/)
| 
    "patient":
       "id": pt_id  
LETTER_UPDATED  
---  
Occurs when a letter is updated.  
Target object | Context object  
    "id": letter_id
    "type": [Letter](/sdk/data-letter/)
| 
    "patient":
       "id": pt_id  
LETTER_ACTION_EVENT_CREATED  
---  
Occurs when a letter action event is created.  
Target object | Context object  
    "id": letter_action_event_id
    "type": [LetterActionEvent](/sdk/data-letter-action-event/)
| 
    empty  
LETTER_ACTION_EVENT_UPDATED  
---  
Occurs when a letter action event is updated.  
Target object | Context object  
    "id": letter_action_event_id
    "type": [LetterActionEvent](/sdk/data-letter-action-event/)
| 
    empty  
####  Observations 
OBSERVATION_CREATED  
---  
Occurs when an observation is created.  
Target object | Context object  
    "id": observation_id
    "type": [Observation](/sdk/data-observation/#observation)
| 
    "patient":
       "id": pt_id  
OBSERVATION_UPDATED  
---  
Occurs when an observation is updated.  
Target object | Context object  
    "id": observation_id
    "type": [Observation](/sdk/data-observation/#observation)
| 
    "patient":
       "id": pt_id  
####  Protocol Overrides 
PROTOCOL_OVERRIDE_CREATED  
---  
Target object | Context object  
    "id": protocoloverride_id
    "type": [ProtocolOverride](/sdk/data-protocol-override/#protocoloverride)
| 
    "patient":
       "id": pt_id  
PROTOCOL_OVERRIDE_UPDATED  
---  
Target object | Context object  
    "id": protocoloverride_id
    "type": [ProtocolOverride](/sdk/data-protocol-override/#protocoloverride)
| 
    "patient":
       "id": pt_id  
PROTOCOL_OVERRIDE_DELETED  
---  
Target object | Context object  
    "id": protocoloverride_id
    "type": [ProtocolOverride](/sdk/data-protocol-override/#protocoloverride)
| 
    "patient":
       "id": pt_id  
####  Referral Reports 
REFERRAL_REPORT_CREATED  
---  
Occurs when a specialist consult report is created in Data Integration.  
Target object | Context object  
    "id": referralreport_id
    "type": [ReferralReport](/sdk/data-referral/#referralreport)
| 
    "patient":
       "id": pt_id  
REFERRAL_REPORT_UPDATED  
---  
Occurs when a specialist consult report is updated.  
Target object | Context object  
    "id": referralreport_id
    "type": [ReferralReport](/sdk/data-referral/#referralreport)
| 
    "patient":
       "id": pt_id  
####  Tasks 
TASK_CREATED  
---  
Occurs when a task is created.  
Target object | Context object  
    "id": task_id
    "type": [Task](/sdk/data-task/#task)
| 
    "patient":
       "id": pt_id  
TASK_UPDATED  
---  
Occurs when a task is updated.  
Target object | Context object  
    "id": task_id
    "type": [Task](/sdk/data-task/#task)
| 
    "patient":
       "id": pt_id  
TASK_COMMENT_CREATED  
---  
Occurs when a comment is added to a task.  
Target object | Context object  
    "id": taskcomment_id
    "type": [TaskComment](/sdk/data-task/#taskcomment)
| 
    empty  
TASK_COMMENT_UPDATED  
---  
Occurs when a comment for a task is updated.  
Target object | Context object  
    "id": taskcomment_id
    "type": [TaskComment](/sdk/data-task/#taskcomment)
| 
    empty  
TASK_COMMENT_DELETED  
---  
Occurs when a comment for a task is removed.  
Target object | Context object  
    "id": taskcomment_id
    "type": [TaskComment](/sdk/data-task/#taskcomment)
| 
    empty  
TASK_LABELS_ADJUSTED  
---  
Occurs when a label is added to or removed from a task. **Note:** unlike the other `TASK_*` events, the target of this event is the `TaskLabel` that changed — _not_ the task. The affected task's ID is available in the context object as `task.id` (use that to load the task, e.g. `Task.objects.get(id=self.event.context["task"]["id"])`), and `action` tells you whether the label was `add`ed or `remove`d.  
Target object | Context object  
    "id": task_label_id
    "type": [TaskLabel](/sdk/data-task/#tasklabel)
| 
    "patient":
       "id": pt_id
    "task":
        "id": task_id
    "action": literal["add", "remove"]  
TASK_COMPLETED  
---  
Occurs when a task is set to completed.  
Target object | Context object  
    "id": task_id
    "type": [Task](/sdk/data-task/#task)
| 
    "patient":
       "id": pt_id  
TASK_CLOSED  
---  
Occurs when a task is set to closed.  
Target object | Context object  
    "id": task_id
    "type": [Task](/sdk/data-task/#task)
| 
    "patient":
       "id": pt_id  
####  Staff 
STAFF_CREATED  
---  
Occurs when a staff is created.  
Target object | Context object  
    "id": staff_id
    "type": [Staff](/sdk/data-staff/#staff)
| 
    empty  
STAFF_UPDATED  
---  
Occurs when a staff is updated.  
Target object | Context object  
    "id": staff_id
    "type": [Staff](/sdk/data-staff/#staff)
| 
    empty  
STAFF_ACTIVATED  
---  
Occurs when a staff record is created with active=True, or a staff record's active field is updated from False to True.  
Target object | Context object  
    "id": staff_id
    "type": [Staff](/sdk/data-staff/#staff)
| 
    empty  
STAFF_DEACTIVATED  
---  
Occurs when a staff record's active field is updated from True to False.  
Target object | Context object  
    "id": staff_id
    "type": [Staff](/sdk/data-staff/#staff)
| 
    empty  
####  Staff External Identifier 
STAFF_EXTERNAL_IDENTIFIER_CREATED  
---  
Occurs when an external identifier is created for a staff member.  
Target object | Context object  
    "id": staffexternalidentifier_id
    "type": [StaffExternalIdentifier](/sdk/data-staff/#staffexternalidentifier)
| 
    "staff":
        "id": staff_id  
STAFF_EXTERNAL_IDENTIFIER_UPDATED  
---  
Occurs when an external identifier for a staff member is updated.  
Target object | Context object  
    "id": staffexternalidentifier_id
    "type": [StaffExternalIdentifier](/sdk/data-staff/#staffexternalidentifier)
| 
    "staff":
        "id": staff_id  
STAFF_EXTERNAL_IDENTIFIER_DELETED  
---  
Occurs when an external identifier for a staff member is deleted.  
Target object | Context object  
    "id": staffexternalidentifier_id
    "type": [StaffExternalIdentifier](/sdk/data-staff/#staffexternalidentifier)
| 
    "staff":
        "id": staff_id  
####  Staff Metadata 
STAFF_METADATA_CREATED  
---  
Occurs when a staff member's metadata is created.  
Target object | Context object  
    "id": staffmetadata_id
    "type": [StaffMetadata](/sdk/data-staff/#staffmetadata)
| 
    "staff":
        "id": staff_id  
STAFF_METADATA_UPDATED  
---  
Occurs when a staff member's metadata is updated.  
Target object | Context object  
    "id": staffmetadata_id
    "type": [StaffMetadata](/sdk/data-staff/#staffmetadata)
| 
    "staff":
        "id": staff_id  
STAFF_METADATA_DELETED  
---  
Occurs when a staff member's metadata is deleted.  
Target object | Context object  
    "id": staffmetadata_id
    "type": [StaffMetadata](/sdk/data-staff/#staffmetadata)
| 
    "staff":
        "id": staff_id  
####  Vital Signs 
VITAL_SIGN_CREATED  
---  
Occurs when a vitals entry is created for a patient using the vitals command. Additional details for the vitals become available with subsequent VITAL_SIGN_UPDATED events.  
Target object | Context object  
    "id": vitalsign_id
    "type": None
| 
    empty  
VITAL_SIGN_UPDATED  
---  
Occurs when a vitals entry is updated for a patient.  
Target object | Context object  
    "id": vitalsign_id
    "type": None
| 
    empty  
###  Command lifecycle events 
These events fire during the command lifecycle.
####  Generic events 
Event | Occurs when | PRE_COMMAND_ORIGINATE | Before any command is entered into a note.  
---|---  
POST_COMMAND_ORIGINATE | After any command is entered into a note.  
PRE_COMMAND_UPDATE | Before the data in any command is updated.  
POST_COMMAND_UPDATE | After the data in any command is updated.  
PRE_COMMAND_COMMIT | Before any command is committed.  
POST_COMMAND_COMMIT | After any command is committed.  
PRE_COMMAND_DELETE | Before any command is deleted.  
POST_COMMAND_DELETE | After any command is deleted.  
PRE_COMMAND_ENTER_IN_ERROR | Before any command is marked as entered in error.  
POST_COMMAND_ENTER_IN_ERROR | After any command is marked as entered in error.  
PRE_COMMAND_EXECUTE_ACTION | Before an action is executed on any command.  
POST_COMMAND_EXECUTE_ACTION | After an action is executed on any command.  
POST_COMMAND_INSERTED_INTO_NOTE | After a command is added to a note in the UI.  
AVAILABLE_ACTIONS | When a command is rendered in the UI, after any update to data, state, or other changes  
#####  Transaction Behavior 
Pre-event handlers (`PRE_COMMAND_ORIGINATE`, `PRE_COMMAND_COMMIT`, `PRE_COMMAND_UPDATE`) run synchronously inside the same database transaction as the command operation. Your handler can perform validation or modify data, and if it raises an exception, both your changes and the command operation roll back together.
Post-event handlers (`POST_COMMAND_ORIGINATE`, `POST_COMMAND_COMMIT`, `POST_COMMAND_UPDATE`) use Django's `on_commit` mechanism and execute only after the outermost transaction commits successfully. If you wrap a command operation inside a `transaction.atomic()` block, the post-event handlers won't fire until that outer transaction commits.
This model lets you combine command operations with other database writes in a single atomic unit. You can originate a command and update related records together, knowing that either all operations succeed or none do.
See [Transactions](/sdk/custom-data-transactions/) for more on using `transaction.atomic()` in your plugins.
#####  Context Overview 
Each command lifecycle event provides specific context to the handler, depending on the stage of the command lifecycle.
**Base Context (All Events Except`PRE_COMMAND_ORIGINATE`)**:
    ```json
    {
      "note": { "uuid": "note-123" },
      "patient": { "id": "patient-123" },
      "fields": { "key": "value" }
    }
    ```
  - `note.uuid`: The unique identifier of the note associated with the command.
  - `patient.id`: The unique identifier of the patient associated with the note.
  - `fields`: A dictionary containing command-specific details. See examples for each command.
**`PRE_COMMAND_ORIGINATE` Context**: Since the command is not yet connected to a note, the `PRE_COMMAND_ORIGINATE` event context only includes:
    ```json
    {
      "fields": { "key": "value" }
    }
    ```
  - `fields`: Contains details specific to the command being originated.
* * *
####  Adjust Prescription Command 
ADJUST_PRESCRIPTION_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "change_medication_to": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ADJUST_PRESCRIPTION_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "change_medication_to": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ADJUST_PRESCRIPTION_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "change_medication_to": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ADJUST_PRESCRIPTION_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
ADJUST_PRESCRIPTION_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "change_medication_to": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ADJUST_PRESCRIPTION_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "change_medication_to": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ADJUST_PRESCRIPTION_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "change_medication_to": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ADJUST_PRESCRIPTION_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "change_medication_to": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ADJUST_PRESCRIPTION_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "change_medication_to": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ADJUST_PRESCRIPTION_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ADJUST_PRESCRIPTION_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "change_medication_to": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ADJUST_PRESCRIPTION_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "change_medication_to": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ADJUST_PRESCRIPTION_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "change_medication_to": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ADJUST_PRESCRIPTION_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "change_medication_to": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ADJUST_PRESCRIPTION_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "change_medication_to": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ADJUST_PRESCRIPTION__INDICATIONS__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
ADJUST_PRESCRIPTION__INDICATIONS__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
ADJUST_PRESCRIPTION__PHARMACY__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
ADJUST_PRESCRIPTION__PHARMACY__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
ADJUST_PRESCRIPTION__PRESCRIBE__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[MedicationSearchResult]  
ADJUST_PRESCRIPTION__PRESCRIBE__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
ADJUST_PRESCRIPTION__CHANGE_MEDICATION_TO__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[MedicationSearchResult]  
ADJUST_PRESCRIPTION__CHANGE_MEDICATION_TO__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
ADJUST_PRESCRIPTION__SUPERVISING_PROVIDER__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
ADJUST_PRESCRIPTION__SUPERVISING_PROVIDER__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
ADJUST_PRESCRIPTION__PRESCRIBER__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
ADJUST_PRESCRIPTION__PRESCRIBER__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Allergy Command 
ALLERGY_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "allergy": dict
      "severity": str
      "narrative": str
      "approximate_date":
        "input": str
        "date": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ALLERGY_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "allergy": dict
      "severity": str
      "narrative": str
      "approximate_date":
        "input": str
        "date": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ALLERGY_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "allergy": dict
      "severity": str
      "narrative": str
      "approximate_date":
        "input": str
        "date": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ALLERGY_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
ALLERGY_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "allergy": dict
      "severity": str
      "narrative": str
      "approximate_date":
        "input": str
        "date": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ALLERGY_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "allergy": dict
      "severity": str
      "narrative": str
      "approximate_date":
        "input": str
        "date": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ALLERGY_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "allergy": dict
      "severity": str
      "narrative": str
      "approximate_date":
        "input": str
        "date": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ALLERGY_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "allergy": dict
      "severity": str
      "narrative": str
      "approximate_date":
        "input": str
        "date": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ALLERGY_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "allergy": dict
      "severity": str
      "narrative": str
      "approximate_date":
        "input": str
        "date": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ALLERGY_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "allergy": dict
      "severity": str
      "narrative": str
      "approximate_date":
        "input": str
        "date": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ALLERGY_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "allergy": dict
      "severity": str
      "narrative": str
      "approximate_date":
        "input": str
        "date": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ALLERGY_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "allergy": dict
      "severity": str
      "narrative": str
      "approximate_date":
        "input": str
        "date": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ALLERGY_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "allergy": dict
      "severity": str
      "narrative": str
      "approximate_date":
        "input": str
        "date": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ALLERGY_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "allergy": dict
      "severity": str
      "narrative": str
      "approximate_date":
        "input": str
        "date": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ALLERGY_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "allergy": dict
      "severity": str
      "narrative": str
      "approximate_date":
        "input": str
        "date": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ALLERGY__ALLERGY__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[AllergySearchResult]  
ALLERGY__ALLERGY__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Assess Command 
ASSESS_COMMAND__CONDITION_SELECTED  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "condition": dict
      "background": str
      "status": str
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ASSESS_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "condition": dict
      "background": str
      "status": str
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ASSESS_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "condition": dict
      "background": str
      "status": str
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ASSESS_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "condition": dict
      "background": str
      "status": str
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ASSESS_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
ASSESS_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "condition": dict
      "background": str
      "status": str
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ASSESS_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "condition": dict
      "background": str
      "status": str
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ASSESS_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "condition": dict
      "background": str
      "status": str
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ASSESS_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "condition": dict
      "background": str
      "status": str
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ASSESS_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "condition": dict
      "background": str
      "status": str
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ASSESS_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "condition": dict
      "background": str
      "status": str
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ASSESS_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "condition": dict
      "background": str
      "status": str
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ASSESS_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "condition": dict
      "background": str
      "status": str
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ASSESS_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "condition": dict
      "background": str
      "status": str
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ASSESS_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "condition": dict
      "background": str
      "status": str
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ASSESS_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "condition": dict
      "background": str
      "status": str
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ASSESS__CONDITION__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[ConditionSearchResult]  
ASSESS__CONDITION__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[ConditionSearchResult]  
####  Cancel Prescription Command 
CANCEL_PRESCRIPTION_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescription": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CANCEL_PRESCRIPTION_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescription": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CANCEL_PRESCRIPTION_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescription": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CANCEL_PRESCRIPTION_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescription": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CANCEL_PRESCRIPTION_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescription": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CANCEL_PRESCRIPTION_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescription": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CANCEL_PRESCRIPTION_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescription": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CANCEL_PRESCRIPTION_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescription": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CANCEL_PRESCRIPTION_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescription": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CANCEL_PRESCRIPTION_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescription": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CANCEL_PRESCRIPTION_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescription": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CANCEL_PRESCRIPTION_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescription": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CANCEL_PRESCRIPTION_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescription": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CANCEL_PRESCRIPTION_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescription": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CANCEL_PRESCRIPTION__SELECTED_PRESCRIPTION__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
CANCEL_PRESCRIPTION__SELECTED_PRESCRIPTION__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Change Medication Command 
CHANGE_MEDICATION_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "sig": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CHANGE_MEDICATION_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "sig": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CHANGE_MEDICATION_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "sig": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CHANGE_MEDICATION_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "sig": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CHANGE_MEDICATION_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "sig": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CHANGE_MEDICATION_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "sig": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CHANGE_MEDICATION_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "sig": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CHANGE_MEDICATION_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "sig": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CHANGE_MEDICATION_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "sig": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CHANGE_MEDICATION_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "sig": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CHANGE_MEDICATION_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "sig": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CHANGE_MEDICATION_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "sig": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CHANGE_MEDICATION_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "sig": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CHANGE_MEDICATION_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "sig": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CHANGE_MEDICATION__MEDICATION__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[MedicationSearchResult]  
CHANGE_MEDICATION__MEDICATION__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Chart Section Review Command 
CHART_SECTION_REVIEW_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "section": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CHART_SECTION_REVIEW_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "section": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CHART_SECTION_REVIEW_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "section": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CHART_SECTION_REVIEW_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "section": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CHART_SECTION_REVIEW_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id  
CHART_SECTION_REVIEW_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "section": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CHART_SECTION_REVIEW_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "section": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CHART_SECTION_REVIEW_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "section": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
####  Clipboard Command 
CLIPBOARD_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "text": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CLIPBOARD_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "text": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CLIPBOARD_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "text": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CLIPBOARD_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
CLIPBOARD_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "text": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CLIPBOARD_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "text": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CLIPBOARD_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "text": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CLIPBOARD_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "text": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CLIPBOARD_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "text": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CLIPBOARD_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "text": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CLIPBOARD_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "text": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CLIPBOARD_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "text": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CLIPBOARD_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "text": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CLIPBOARD_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "text": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CLIPBOARD_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "text": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
#####  Clipboard Fields Context 
The Clipboard Command provides the following fields in its context:
Field | Type | Description  
---|---|---  
`text` | _string_ | The raw text content copied to the clipboard.  
Refer to the base context documentation for additional details about the full context structure.
    ```json
    {
      "note": { "uuid": "note-123" },
      "patient": { "id": "patient-123" },
      "fields": {
        "text": "Patient complains of persistent headaches for the past two weeks."
      }
    }
    ```
* * *
####  Close Goal Command 
CLOSE_GOAL_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_id": dict
      "achievement_status": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CLOSE_GOAL_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_id": dict
      "achievement_status": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CLOSE_GOAL_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_id": dict
      "achievement_status": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CLOSE_GOAL_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
CLOSE_GOAL_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_id": dict
      "achievement_status": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CLOSE_GOAL_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_id": dict
      "achievement_status": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CLOSE_GOAL_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_id": dict
      "achievement_status": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CLOSE_GOAL_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_id": dict
      "achievement_status": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CLOSE_GOAL_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_id": dict
      "achievement_status": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CLOSE_GOAL_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_id": dict
      "achievement_status": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CLOSE_GOAL_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_id": dict
      "achievement_status": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CLOSE_GOAL_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_id": dict
      "achievement_status": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CLOSE_GOAL_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_id": dict
      "achievement_status": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CLOSE_GOAL_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_id": dict
      "achievement_status": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CLOSE_GOAL_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_id": dict
      "achievement_status": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CLOSE_GOAL__GOAL_ID__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
CLOSE_GOAL__GOAL_ID__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Assess Coding Gap Command 
ASSESS_CODING_GAP_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
      "status": str
      "details": str
      "diagnose": list
      "background": str
      "approximate_date_of_onset": str
      "todays_assessment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ASSESS_CODING_GAP_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
      "status": str
      "details": str
      "diagnose": list
      "background": str
      "approximate_date_of_onset": str
      "todays_assessment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ASSESS_CODING_GAP_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
      "status": str
      "details": str
      "diagnose": list
      "background": str
      "approximate_date_of_onset": str
      "todays_assessment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ASSESS_CODING_GAP_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
      "status": str
      "details": str
      "diagnose": list
      "background": str
      "approximate_date_of_onset": str
      "todays_assessment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ASSESS_CODING_GAP_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
      "status": str
      "details": str
      "diagnose": list
      "background": str
      "approximate_date_of_onset": str
      "todays_assessment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ASSESS_CODING_GAP_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
      "status": str
      "details": str
      "diagnose": list
      "background": str
      "approximate_date_of_onset": str
      "todays_assessment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ASSESS_CODING_GAP_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
      "status": str
      "details": str
      "diagnose": list
      "background": str
      "approximate_date_of_onset": str
      "todays_assessment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ASSESS_CODING_GAP_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
      "status": str
      "details": str
      "diagnose": list
      "background": str
      "approximate_date_of_onset": str
      "todays_assessment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ASSESS_CODING_GAP_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
      "status": str
      "details": str
      "diagnose": list
      "background": str
      "approximate_date_of_onset": str
      "todays_assessment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ASSESS_CODING_GAP_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
      "status": str
      "details": str
      "diagnose": list
      "background": str
      "approximate_date_of_onset": str
      "todays_assessment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ASSESS_CODING_GAP_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
      "status": str
      "details": str
      "diagnose": list
      "background": str
      "approximate_date_of_onset": str
      "todays_assessment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ASSESS_CODING_GAP_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
      "status": str
      "details": str
      "diagnose": list
      "background": str
      "approximate_date_of_onset": str
      "todays_assessment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ASSESS_CODING_GAP_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
      "status": str
      "details": str
      "diagnose": list
      "background": str
      "approximate_date_of_onset": str
      "todays_assessment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ASSESS_CODING_GAP_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
      "status": str
      "details": str
      "diagnose": list
      "background": str
      "approximate_date_of_onset": str
      "todays_assessment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
####  Create Coding Gap Command 
CREATE_CODING_GAP_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnose": list
      "status": str
      "date": str
      "details": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CREATE_CODING_GAP_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnose": list
      "status": str
      "date": str
      "details": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CREATE_CODING_GAP_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnose": list
      "status": str
      "date": str
      "details": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CREATE_CODING_GAP_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnose": list
      "status": str
      "date": str
      "details": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CREATE_CODING_GAP_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnose": list
      "status": str
      "date": str
      "details": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CREATE_CODING_GAP_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnose": list
      "status": str
      "date": str
      "details": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CREATE_CODING_GAP_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnose": list
      "status": str
      "date": str
      "details": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CREATE_CODING_GAP_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnose": list
      "status": str
      "date": str
      "details": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CREATE_CODING_GAP_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnose": list
      "status": str
      "date": str
      "details": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CREATE_CODING_GAP_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnose": list
      "status": str
      "date": str
      "details": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CREATE_CODING_GAP_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnose": list
      "status": str
      "date": str
      "details": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CREATE_CODING_GAP_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnose": list
      "status": str
      "date": str
      "details": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CREATE_CODING_GAP_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnose": list
      "status": str
      "date": str
      "details": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CREATE_CODING_GAP_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnose": list
      "status": str
      "date": str
      "details": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
####  Defer Coding Gap Command 
DEFER_CODING_GAP_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DEFER_CODING_GAP_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DEFER_CODING_GAP_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DEFER_CODING_GAP_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DEFER_CODING_GAP_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DEFER_CODING_GAP_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DEFER_CODING_GAP_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DEFER_CODING_GAP_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DEFER_CODING_GAP_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DEFER_CODING_GAP_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DEFER_CODING_GAP_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DEFER_CODING_GAP_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DEFER_CODING_GAP_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DEFER_CODING_GAP_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
####  Validate Coding Gap Command 
VALIDATE_CODING_GAP_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
      "status": str
      "date": str
      "details": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VALIDATE_CODING_GAP_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
      "status": str
      "date": str
      "details": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VALIDATE_CODING_GAP_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
      "status": str
      "date": str
      "details": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VALIDATE_CODING_GAP_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
      "status": str
      "date": str
      "details": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VALIDATE_CODING_GAP_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
      "status": str
      "date": str
      "details": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VALIDATE_CODING_GAP_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
      "status": str
      "date": str
      "details": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VALIDATE_CODING_GAP_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
      "status": str
      "date": str
      "details": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VALIDATE_CODING_GAP_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
      "status": str
      "date": str
      "details": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VALIDATE_CODING_GAP_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
      "status": str
      "date": str
      "details": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VALIDATE_CODING_GAP_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
      "status": str
      "date": str
      "details": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VALIDATE_CODING_GAP_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
      "status": str
      "date": str
      "details": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VALIDATE_CODING_GAP_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
      "status": str
      "date": str
      "details": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VALIDATE_CODING_GAP_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
      "status": str
      "date": str
      "details": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VALIDATE_CODING_GAP_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "detected_issue": str
      "status": str
      "date": str
      "details": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
####  Custom Command 
CUSTOM_COMMAND_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "content": str
      "schema_key": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CUSTOM_COMMAND_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "content": str
      "schema_key": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CUSTOM_COMMAND_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "content": str
      "schema_key": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CUSTOM_COMMAND_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "content": str
      "schema_key": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CUSTOM_COMMAND_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "content": str
      "schema_key": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CUSTOM_COMMAND_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "content": str
      "schema_key": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CUSTOM_COMMAND_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "content": str
      "schema_key": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CUSTOM_COMMAND_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "content": str
      "schema_key": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CUSTOM_COMMAND_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "content": str
      "schema_key": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CUSTOM_COMMAND_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "content": str
      "schema_key": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CUSTOM_COMMAND_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "content": str
      "schema_key": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CUSTOM_COMMAND_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "content": str
      "schema_key": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CUSTOM_COMMAND_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "content": str
      "schema_key": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
CUSTOM_COMMAND_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": str
    "user":
      "staff": staff_id  
####  Diagnose Command 
DIAGNOSE_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnose": dict
      "background": str
      "approximate_date_of_onset":
        "input": str
        "date": str
      "today_assessment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DIAGNOSE_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnose": dict
      "background": str
      "approximate_date_of_onset":
        "input": str
        "date": str
      "today_assessment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DIAGNOSE_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnose": dict
      "background": str
      "approximate_date_of_onset":
        "input": str
        "date": str
      "today_assessment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DIAGNOSE_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
DIAGNOSE_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnose": dict
      "background": str
      "approximate_date_of_onset":
        "input": str
        "date": str
      "today_assessment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DIAGNOSE_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnose": dict
      "background": str
      "approximate_date_of_onset":
        "input": str
        "date": str
      "today_assessment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DIAGNOSE_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnose": dict
      "background": str
      "approximate_date_of_onset":
        "input": str
        "date": str
      "today_assessment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DIAGNOSE_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnose": dict
      "background": str
      "approximate_date_of_onset":
        "input": str
        "date": str
      "today_assessment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DIAGNOSE_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnose": dict
      "background": str
      "approximate_date_of_onset":
        "input": str
        "date": str
      "today_assessment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DIAGNOSE_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnose": dict
      "background": str
      "approximate_date_of_onset":
        "input": str
        "date": str
      "today_assessment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DIAGNOSE_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnose": dict
      "background": str
      "approximate_date_of_onset":
        "input": str
        "date": str
      "today_assessment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DIAGNOSE_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnose": dict
      "background": str
      "approximate_date_of_onset":
        "input": str
        "date": str
      "today_assessment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DIAGNOSE_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnose": dict
      "background": str
      "approximate_date_of_onset":
        "input": str
        "date": str
      "today_assessment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DIAGNOSE_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnose": dict
      "background": str
      "approximate_date_of_onset":
        "input": str
        "date": str
      "today_assessment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DIAGNOSE_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnose": dict
      "background": str
      "approximate_date_of_onset":
        "input": str
        "date": str
      "today_assessment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DIAGNOSE__DIAGNOSE__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[ConditionSearchResult]  
DIAGNOSE__DIAGNOSE__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Educational Material Command 
EDUCATIONAL_MATERIAL_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
EDUCATIONAL_MATERIAL_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
EDUCATIONAL_MATERIAL_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
EDUCATIONAL_MATERIAL_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
EDUCATIONAL_MATERIAL_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
EDUCATIONAL_MATERIAL_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
EDUCATIONAL_MATERIAL_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
EDUCATIONAL_MATERIAL_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
EDUCATIONAL_MATERIAL_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
EDUCATIONAL_MATERIAL_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
EDUCATIONAL_MATERIAL_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
EDUCATIONAL_MATERIAL_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
EDUCATIONAL_MATERIAL_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
EDUCATIONAL_MATERIAL_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
EDUCATIONAL_MATERIAL_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
EDUCATIONAL_MATERIAL__LANGUAGE__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
EDUCATIONAL_MATERIAL__LANGUAGE__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
EDUCATIONAL_MATERIAL__TITLE__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
EDUCATIONAL_MATERIAL__TITLE__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Family History Command 
FAMILY_HISTORY_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "family_history": dict
      "relative": dict
      "note": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
FAMILY_HISTORY_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "family_history": dict
      "relative": dict
      "note": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
FAMILY_HISTORY_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "family_history": dict
      "relative": dict
      "note": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
FAMILY_HISTORY_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
FAMILY_HISTORY_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "family_history": dict
      "relative": dict
      "note": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
FAMILY_HISTORY_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "family_history": dict
      "relative": dict
      "note": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
FAMILY_HISTORY_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "family_history": dict
      "relative": dict
      "note": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
FAMILY_HISTORY_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "family_history": dict
      "relative": dict
      "note": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
FAMILY_HISTORY_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "family_history": dict
      "relative": dict
      "note": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
FAMILY_HISTORY_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "family_history": dict
      "relative": dict
      "note": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
FAMILY_HISTORY_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "family_history": dict
      "relative": dict
      "note": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
FAMILY_HISTORY_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "family_history": dict
      "relative": dict
      "note": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
FAMILY_HISTORY_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "family_history": dict
      "relative": dict
      "note": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
FAMILY_HISTORY_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "family_history": dict
      "relative": dict
      "note": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
FAMILY_HISTORY_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "family_history": dict
      "relative": dict
      "note": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
FAMILY_HISTORY__FAMILY_HISTORY__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
FAMILY_HISTORY__FAMILY_HISTORY__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
FAMILY_HISTORY__RELATIVE__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
FAMILY_HISTORY__RELATIVE__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Follow Up Command 
FOLLOW_UP_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "requested_date": dict
      "note_type": dict
      "coding": dict
      "reason_for_visit": dict
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
FOLLOW_UP_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "requested_date": dict
      "note_type": dict
      "coding": dict
      "reason_for_visit": dict
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
FOLLOW_UP_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "requested_date": dict
      "note_type": dict
      "coding": dict
      "reason_for_visit": dict
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
FOLLOW_UP_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
FOLLOW_UP_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "requested_date": dict
      "note_type": dict
      "coding": dict
      "reason_for_visit": dict
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
FOLLOW_UP_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "requested_date": dict
      "note_type": dict
      "coding": dict
      "reason_for_visit": dict
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
FOLLOW_UP_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "requested_date": dict
      "note_type": dict
      "coding": dict
      "reason_for_visit": dict
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
FOLLOW_UP_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "requested_date": dict
      "note_type": dict
      "coding": dict
      "reason_for_visit": dict
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
FOLLOW_UP_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "requested_date": dict
      "note_type": dict
      "coding": dict
      "reason_for_visit": dict
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
FOLLOW_UP_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "requested_date": dict
      "note_type": dict
      "coding": dict
      "reason_for_visit": dict
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
FOLLOW_UP_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "requested_date": dict
      "note_type": dict
      "coding": dict
      "reason_for_visit": dict
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
FOLLOW_UP_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "requested_date": dict
      "note_type": dict
      "coding": dict
      "reason_for_visit": dict
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
FOLLOW_UP_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "requested_date": dict
      "note_type": dict
      "coding": dict
      "reason_for_visit": dict
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
FOLLOW_UP_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "requested_date": dict
      "note_type": dict
      "coding": dict
      "reason_for_visit": dict
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
FOLLOW_UP_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "requested_date": dict
      "note_type": dict
      "coding": dict
      "reason_for_visit": dict
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
FOLLOW_UP__CODING__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
FOLLOW_UP__CODING__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
FOLLOW_UP__NOTE_TYPE__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
FOLLOW_UP__NOTE_TYPE__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Goal Command 
GOAL_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_statement": str
      "start_date": str
      "due_date": str
      "achievement_status": str
      "priority": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
GOAL_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_statement": str
      "start_date": str
      "due_date": str
      "achievement_status": str
      "priority": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
GOAL_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_statement": str
      "start_date": str
      "due_date": str
      "achievement_status": str
      "priority": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
GOAL_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
GOAL_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_statement": str
      "start_date": str
      "due_date": str
      "achievement_status": str
      "priority": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
GOAL_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_statement": str
      "start_date": str
      "due_date": str
      "achievement_status": str
      "priority": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
GOAL_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_statement": str
      "start_date": str
      "due_date": str
      "achievement_status": str
      "priority": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
GOAL_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_statement": str
      "start_date": str
      "due_date": str
      "achievement_status": str
      "priority": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
GOAL_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_statement": str
      "start_date": str
      "due_date": str
      "achievement_status": str
      "priority": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
GOAL_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_statement": str
      "start_date": str
      "due_date": str
      "achievement_status": str
      "priority": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
GOAL_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_statement": str
      "start_date": str
      "due_date": str
      "achievement_status": str
      "priority": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
GOAL_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_statement": str
      "start_date": str
      "due_date": str
      "achievement_status": str
      "priority": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
GOAL_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_statement": str
      "start_date": str
      "due_date": str
      "achievement_status": str
      "priority": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
GOAL_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_statement": str
      "start_date": str
      "due_date": str
      "achievement_status": str
      "priority": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
GOAL_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_statement": str
      "start_date": str
      "due_date": str
      "achievement_status": str
      "priority": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
####  History of Present Illness Command 
HISTORY_OF_PRESENT_ILLNESS_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
HISTORY_OF_PRESENT_ILLNESS_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
HISTORY_OF_PRESENT_ILLNESS_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
HISTORY_OF_PRESENT_ILLNESS_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
HISTORY_OF_PRESENT_ILLNESS_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
HISTORY_OF_PRESENT_ILLNESS_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
HISTORY_OF_PRESENT_ILLNESS_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
HISTORY_OF_PRESENT_ILLNESS_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
HISTORY_OF_PRESENT_ILLNESS_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
HISTORY_OF_PRESENT_ILLNESS_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
HISTORY_OF_PRESENT_ILLNESS_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
HISTORY_OF_PRESENT_ILLNESS_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
HISTORY_OF_PRESENT_ILLNESS_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
HISTORY_OF_PRESENT_ILLNESS_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
HISTORY_OF_PRESENT_ILLNESS_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
####  Imaging Order Command 
IMAGING_ORDER_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "image": dict
      "indications": list[dict]
      "priority": str
      "additional_details": str
      "imaging_center": dict
      "comment": str
      "ordering_provider": dict
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMAGING_ORDER_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "image": dict
      "indications": list[dict]
      "priority": str
      "additional_details": str
      "imaging_center": dict
      "comment": str
      "ordering_provider": dict
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMAGING_ORDER_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "image": dict
      "indications": list[dict]
      "priority": str
      "additional_details": str
      "imaging_center": dict
      "comment": str
      "ordering_provider": dict
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMAGING_ORDER_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
IMAGING_ORDER_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "image": dict
      "indications": list[dict]
      "priority": str
      "additional_details": str
      "imaging_center": dict
      "comment": str
      "ordering_provider": dict
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMAGING_ORDER_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "image": dict
      "indications": list[dict]
      "priority": str
      "additional_details": str
      "imaging_center": dict
      "comment": str
      "ordering_provider": dict
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMAGING_ORDER_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "image": dict
      "indications": list[dict]
      "priority": str
      "additional_details": str
      "imaging_center": dict
      "comment": str
      "ordering_provider": dict
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMAGING_ORDER_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "image": dict
      "indications": list[dict]
      "priority": str
      "additional_details": str
      "imaging_center": dict
      "comment": str
      "ordering_provider": dict
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMAGING_ORDER_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "image": dict
      "indications": list[dict]
      "priority": str
      "additional_details": str
      "imaging_center": dict
      "comment": str
      "ordering_provider": dict
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMAGING_ORDER_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "image": dict
      "indications": list[dict]
      "priority": str
      "additional_details": str
      "imaging_center": dict
      "comment": str
      "ordering_provider": dict
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMAGING_ORDER_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "image": dict
      "indications": list[dict]
      "priority": str
      "additional_details": str
      "imaging_center": dict
      "comment": str
      "ordering_provider": dict
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMAGING_ORDER_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "image": dict
      "indications": list[dict]
      "priority": str
      "additional_details": str
      "imaging_center": dict
      "comment": str
      "ordering_provider": dict
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMAGING_ORDER_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "image": dict
      "indications": list[dict]
      "priority": str
      "additional_details": str
      "imaging_center": dict
      "comment": str
      "ordering_provider": dict
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMAGING_ORDER_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "image": dict
      "indications": list[dict]
      "priority": str
      "additional_details": str
      "imaging_center": dict
      "comment": str
      "ordering_provider": dict
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMAGING_ORDER_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "image": dict
      "indications": list[dict]
      "priority": str
      "additional_details": str
      "imaging_center": dict
      "comment": str
      "ordering_provider": dict
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMAGING_ORDER__IMAGE__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
IMAGING_ORDER__IMAGE__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
IMAGING_ORDER__IMAGING_CENTER__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
IMAGING_ORDER__IMAGING_CENTER__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
IMAGING_ORDER__INDICATIONS__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
IMAGING_ORDER__INDICATIONS__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
IMAGING_ORDER__ORDERING_PROVIDER__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
IMAGING_ORDER__ORDERING_PROVIDER__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Imaging Review Command 
IMAGING_REVIEW_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMAGING_REVIEW_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMAGING_REVIEW_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMAGING_REVIEW_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMAGING_REVIEW_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMAGING_REVIEW_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMAGING_REVIEW_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMAGING_REVIEW_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMAGING_REVIEW_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMAGING_REVIEW_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMAGING_REVIEW_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMAGING_REVIEW_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMAGING_REVIEW__REPORT__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
IMAGING_REVIEW__REPORT__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
IMAGING_REVIEW__COMMUNICATION_METHOD__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
IMAGING_REVIEW__COMMUNICATION_METHOD__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Immunization Statement Command 
IMMUNIZATION_STATEMENT_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "statement": dict
      "date":
        "date": str
        "input": str
      "comments": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMMUNIZATION_STATEMENT_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "statement": dict
      "date":
        "date": str
        "input": str
      "comments": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMMUNIZATION_STATEMENT_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "statement": dict
      "date":
        "date": str
        "input": str
      "comments": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMMUNIZATION_STATEMENT_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "statement": dict
      "date":
        "date": str
        "input": str
      "comments": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMMUNIZATION_STATEMENT_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "statement": dict
      "date":
        "date": str
        "input": str
      "comments": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMMUNIZATION_STATEMENT_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "statement": dict
      "date":
        "date": str
        "input": str
      "comments": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMMUNIZATION_STATEMENT_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "statement": dict
      "date":
        "date": str
        "input": str
      "comments": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMMUNIZATION_STATEMENT_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "statement": dict
      "date":
        "date": str
        "input": str
      "comments": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMMUNIZATION_STATEMENT_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "statement": dict
      "date":
        "date": str
        "input": str
      "comments": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMMUNIZATION_STATEMENT_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "statement": dict
      "date":
        "date": str
        "input": str
      "comments": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMMUNIZATION_STATEMENT_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "statement": dict
      "date":
        "date": str
        "input": str
      "comments": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMMUNIZATION_STATEMENT_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "statement": dict
      "date":
        "date": str
        "input": str
      "comments": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMMUNIZATION_STATEMENT_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "statement": dict
      "date":
        "date": str
        "input": str
      "comments": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMMUNIZATION_STATEMENT_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "statement": dict
      "date":
        "date": str
        "input": str
      "comments": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMMUNIZATION_STATEMENT__STATEMENT__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
IMMUNIZATION_STATEMENT__STATEMENT__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
IMMUNIZATION_STATEMENT_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
####  Immunize Command 
IMMUNIZE_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "coding": dict
      "lot_number": dict
      "manufacturer": str
      "exp_date_original": str
      "sig_original": str
      "consent_given": bool
      "given_by": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMMUNIZE_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "coding": dict
      "lot_number": dict
      "manufacturer": str
      "exp_date_original": str
      "sig_original": str
      "consent_given": bool
      "given_by": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMMUNIZE_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "coding": dict
      "lot_number": dict
      "manufacturer": str
      "exp_date_original": str
      "sig_original": str
      "consent_given": bool
      "given_by": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMMUNIZE_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
IMMUNIZE_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "coding": dict
      "lot_number": dict
      "manufacturer": str
      "exp_date_original": str
      "sig_original": str
      "consent_given": bool
      "given_by": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMMUNIZE_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "coding": dict
      "lot_number": dict
      "manufacturer": str
      "exp_date_original": str
      "sig_original": str
      "consent_given": bool
      "given_by": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMMUNIZE_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "coding": dict
      "lot_number": dict
      "manufacturer": str
      "exp_date_original": str
      "sig_original": str
      "consent_given": bool
      "given_by": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMMUNIZE_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "coding": dict
      "lot_number": dict
      "manufacturer": str
      "exp_date_original": str
      "sig_original": str
      "consent_given": bool
      "given_by": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMMUNIZE_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "coding": dict
      "lot_number": dict
      "manufacturer": str
      "exp_date_original": str
      "sig_original": str
      "consent_given": bool
      "given_by": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMMUNIZE_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "coding": dict
      "lot_number": dict
      "manufacturer": str
      "exp_date_original": str
      "sig_original": str
      "consent_given": bool
      "given_by": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMMUNIZE_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "coding": dict
      "lot_number": dict
      "manufacturer": str
      "exp_date_original": str
      "sig_original": str
      "consent_given": bool
      "given_by": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMMUNIZE_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "coding": dict
      "lot_number": dict
      "manufacturer": str
      "exp_date_original": str
      "sig_original": str
      "consent_given": bool
      "given_by": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMMUNIZE_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "coding": dict
      "lot_number": dict
      "manufacturer": str
      "exp_date_original": str
      "sig_original": str
      "consent_given": bool
      "given_by": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMMUNIZE_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "coding": dict
      "lot_number": dict
      "manufacturer": str
      "exp_date_original": str
      "sig_original": str
      "consent_given": bool
      "given_by": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMMUNIZE_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "coding": dict
      "lot_number": dict
      "manufacturer": str
      "exp_date_original": str
      "sig_original": str
      "consent_given": bool
      "given_by": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
IMMUNIZE__CODING__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
IMMUNIZE__CODING__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
IMMUNIZE__GIVEN_BY__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
IMMUNIZE__GIVEN_BY__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
IMMUNIZE__LOT_NUMBER__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
IMMUNIZE__LOT_NUMBER__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Instruct Command 
INSTRUCT_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "instruct": dict
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
INSTRUCT_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "instruct": dict
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
INSTRUCT_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "instruct": dict
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
INSTRUCT_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
INSTRUCT_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "instruct": dict
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
INSTRUCT_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "instruct": dict
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
INSTRUCT_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "instruct": dict
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
INSTRUCT_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "instruct": dict
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
INSTRUCT_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "instruct": dict
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
INSTRUCT_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "instruct": dict
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
INSTRUCT_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "instruct": dict
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
INSTRUCT_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "instruct": dict
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
INSTRUCT_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "instruct": dict
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
INSTRUCT_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "instruct": dict
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
INSTRUCT_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "instruct": dict
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
INSTRUCT__INSTRUCT__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
INSTRUCT__INSTRUCT__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Lab Order Command 
LAB_ORDER_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "lab_partner": dict
      "tests": list[dict]
      "ordering_provider": dict
      "diagnosis": list[dict]
      "fasting_status": bool
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
LAB_ORDER_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "lab_partner": dict
      "tests": list[dict]
      "ordering_provider": dict
      "diagnosis": list[dict]
      "fasting_status": bool
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
LAB_ORDER_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "lab_partner": dict
      "tests": list[dict]
      "ordering_provider": dict
      "diagnosis": list[dict]
      "fasting_status": bool
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
LAB_ORDER_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
LAB_ORDER_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "lab_partner": dict
      "tests": list[dict]
      "ordering_provider": dict
      "diagnosis": list[dict]
      "fasting_status": bool
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
LAB_ORDER_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "lab_partner": dict
      "tests": list[dict]
      "ordering_provider": dict
      "diagnosis": list[dict]
      "fasting_status": bool
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
LAB_ORDER_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "lab_partner": dict
      "tests": list[dict]
      "ordering_provider": dict
      "diagnosis": list[dict]
      "fasting_status": bool
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
LAB_ORDER_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "lab_partner": dict
      "tests": list[dict]
      "ordering_provider": dict
      "diagnosis": list[dict]
      "fasting_status": bool
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
LAB_ORDER_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "lab_partner": dict
      "tests": list[dict]
      "ordering_provider": dict
      "diagnosis": list[dict]
      "fasting_status": bool
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
LAB_ORDER_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "lab_partner": dict
      "tests": list[dict]
      "ordering_provider": dict
      "diagnosis": list[dict]
      "fasting_status": bool
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
LAB_ORDER_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "lab_partner": dict
      "tests": list[dict]
      "ordering_provider": dict
      "diagnosis": list[dict]
      "fasting_status": bool
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
LAB_ORDER_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "lab_partner": dict
      "tests": list[dict]
      "ordering_provider": dict
      "diagnosis": list[dict]
      "fasting_status": bool
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
LAB_ORDER_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "lab_partner": dict
      "tests": list[dict]
      "ordering_provider": dict
      "diagnosis": list[dict]
      "fasting_status": bool
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
LAB_ORDER_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "lab_partner": dict
      "tests": list[dict]
      "ordering_provider": dict
      "diagnosis": list[dict]
      "fasting_status": bool
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
LAB_ORDER_COMMAND__PRE_SEND  
---  
Fires from Canvas right before a lab order's FHIR `RequestGroup` is built and POSTed to Health Gorilla. Plugins may respond with one or more [HealthGorillaLabOrderOverride](/sdk/effect-health-gorilla-lab-order-override/) effects to inject account numbers, bill-to, performer organization, sub-tenant, or location into the outbound payload.  
Target object | Context object  
    "id": laborder_id
    "type": [LabOrder](/sdk/data-labs/#laborder)
| 
    "lab_order":
      "id": laborder_id
      "uuid": laborder_id
    "lab_partner": str
    "note":
      "id": note_id
      "uuid": note_id
    "patient":
      "id": pt_id  
HEALTH_GORILLA_LAB_ORDER_PREPARED  
---  
Fires from Canvas right after the outbound Health Gorilla FHIR `RequestGroup` dict is constructed and right before it is POSTed to Health Gorilla. Read-only — any effects returned by handlers are discarded. Complements `LAB_ORDER_COMMAND__PRE_SEND`, which fires before the build and accepts override effects.  
Target object | Context object  
    "id": laborder_id
    "type": [LabOrder](/sdk/data-labs/#laborder)
| 
    "lab_order":
      "id": laborder_id
      "uuid": laborder_id
    "lab_partner": str
    "note":
      "id": note_id
      "uuid": note_id
    "patient":
      "id": pt_id
    "request_group": dict  
LAB_ORDER_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "lab_partner": dict
      "tests": list[dict]
      "ordering_provider": dict
      "diagnosis": list[dict]
      "fasting_status": bool
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
LAB_ORDER__DIAGNOSIS__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
LAB_ORDER__DIAGNOSIS__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
LAB_ORDER__LAB_PARTNER__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
LAB_ORDER__LAB_PARTNER__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
LAB_ORDER__ORDERING_PROVIDER__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
LAB_ORDER__ORDERING_PROVIDER__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
LAB_ORDER__TESTS__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
LAB_ORDER__TESTS__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Lab Review Command 
LAB_REVIEW_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
LAB_REVIEW_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
LAB_REVIEW_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
LAB_REVIEW_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
LAB_REVIEW_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
LAB_REVIEW_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
LAB_REVIEW_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
LAB_REVIEW_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
LAB_REVIEW_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
LAB_REVIEW_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
LAB_REVIEW_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
LAB_REVIEW_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
LAB_REVIEW__REPORT__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
LAB_REVIEW__REPORT__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
LAB_REVIEW__COMMUNICATION_METHOD__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
LAB_REVIEW__COMMUNICATION_METHOD__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Medical History Command 
MEDICAL_HISTORY_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "past_medical_history": dict
      "approximate_start_date":
        "date": str
        "input": str
      "approximate_end_date":
        "date": str
        "input": str
      "show_on_condition_list": bool
      "comments": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
MEDICAL_HISTORY_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "past_medical_history": dict
      "approximate_start_date":
        "date": str
        "input": str
      "approximate_end_date":
        "date": str
        "input": str
      "show_on_condition_list": bool
      "comments": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
MEDICAL_HISTORY_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "past_medical_history": dict
      "approximate_start_date":
        "date": str
        "input": str
      "approximate_end_date":
        "date": str
        "input": str
      "show_on_condition_list": bool
      "comments": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
MEDICAL_HISTORY_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
MEDICAL_HISTORY_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "past_medical_history": dict
      "approximate_start_date":
        "date": str
        "input": str
      "approximate_end_date":
        "date": str
        "input": str
      "show_on_condition_list": bool
      "comments": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
MEDICAL_HISTORY_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "past_medical_history": dict
      "approximate_start_date":
        "date": str
        "input": str
      "approximate_end_date":
        "date": str
        "input": str
      "show_on_condition_list": bool
      "comments": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
MEDICAL_HISTORY_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "past_medical_history": dict
      "approximate_start_date":
        "date": str
        "input": str
      "approximate_end_date":
        "date": str
        "input": str
      "show_on_condition_list": bool
      "comments": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
MEDICAL_HISTORY_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "past_medical_history": dict
      "approximate_start_date":
        "date": str
        "input": str
      "approximate_end_date":
        "date": str
        "input": str
      "show_on_condition_list": bool
      "comments": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
MEDICAL_HISTORY_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "past_medical_history": dict
      "approximate_start_date":
        "date": str
        "input": str
      "approximate_end_date":
        "date": str
        "input": str
      "show_on_condition_list": bool
      "comments": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
MEDICAL_HISTORY_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "past_medical_history": dict
      "approximate_start_date":
        "date": str
        "input": str
      "approximate_end_date":
        "date": str
        "input": str
      "show_on_condition_list": bool
      "comments": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
MEDICAL_HISTORY_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "past_medical_history": dict
      "approximate_start_date":
        "date": str
        "input": str
      "approximate_end_date":
        "date": str
        "input": str
      "show_on_condition_list": bool
      "comments": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
MEDICAL_HISTORY_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "past_medical_history": dict
      "approximate_start_date":
        "date": str
        "input": str
      "approximate_end_date":
        "date": str
        "input": str
      "show_on_condition_list": bool
      "comments": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
MEDICAL_HISTORY_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "past_medical_history": dict
      "approximate_start_date":
        "date": str
        "input": str
      "approximate_end_date":
        "date": str
        "input": str
      "show_on_condition_list": bool
      "comments": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
MEDICAL_HISTORY_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "past_medical_history": dict
      "approximate_start_date":
        "date": str
        "input": str
      "approximate_end_date":
        "date": str
        "input": str
      "show_on_condition_list": bool
      "comments": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
MEDICAL_HISTORY_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "past_medical_history": dict
      "approximate_start_date":
        "date": str
        "input": str
      "approximate_end_date":
        "date": str
        "input": str
      "show_on_condition_list": bool
      "comments": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
MEDICAL_HISTORY__APPROXIMATE_END_DATE__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
MEDICAL_HISTORY__APPROXIMATE_END_DATE__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
MEDICAL_HISTORY__APPROXIMATE_START_DATE__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
MEDICAL_HISTORY__APPROXIMATE_START_DATE__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
MEDICAL_HISTORY__PAST_MEDICAL_HISTORY__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[ConditionSearchResult]  
MEDICAL_HISTORY__PAST_MEDICAL_HISTORY__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Medication Statement Command 
MEDICATION_STATEMENT_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "sig": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
MEDICATION_STATEMENT_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "sig": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
MEDICATION_STATEMENT_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "sig": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
MEDICATION_STATEMENT_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
MEDICATION_STATEMENT_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "sig": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
MEDICATION_STATEMENT_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "sig": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
MEDICATION_STATEMENT_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "sig": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
MEDICATION_STATEMENT_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "sig": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
MEDICATION_STATEMENT_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "sig": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
MEDICATION_STATEMENT_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "sig": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
MEDICATION_STATEMENT_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "sig": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
MEDICATION_STATEMENT_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "sig": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
MEDICATION_STATEMENT_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "sig": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
MEDICATION_STATEMENT_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "sig": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
MEDICATION_STATEMENT_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "sig": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
MEDICATION_STATEMENT__MEDICATION__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[MedicationSearchResult]  
MEDICATION_STATEMENT__MEDICATION__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[MedicationSearchResult]  
####  Perform Command 
PERFORM_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "perform": dict
      "notes": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PERFORM_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "perform": dict
      "notes": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PERFORM_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "perform": dict
      "notes": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PERFORM_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
PERFORM_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "perform": dict
      "notes": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PERFORM_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "perform": dict
      "notes": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PERFORM_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "perform": dict
      "notes": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PERFORM_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "perform": dict
      "notes": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PERFORM_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "perform": dict
      "notes": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PERFORM_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "perform": dict
      "notes": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PERFORM_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "perform": dict
      "notes": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PERFORM_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "perform": dict
      "notes": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PERFORM_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "perform": dict
      "notes": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PERFORM_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "perform": dict
      "notes": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PERFORM_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "perform": dict
      "notes": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PERFORM__PERFORM__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
PERFORM__PERFORM__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Physical Exam Command 
PHYSICAL_EXAM_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PHYSICAL_EXAM_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PHYSICAL_EXAM_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PHYSICAL_EXAM_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
PHYSICAL_EXAM_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PHYSICAL_EXAM_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PHYSICAL_EXAM_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PHYSICAL_EXAM_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PHYSICAL_EXAM_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PHYSICAL_EXAM_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PHYSICAL_EXAM_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PHYSICAL_EXAM_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PHYSICAL_EXAM_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PHYSICAL_EXAM_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PHYSICAL_EXAM_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PHYSICAL_EXAM__QUESTIONNAIRE__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
PHYSICAL_EXAM__QUESTIONNAIRE__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Plan Command 
PLAN_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PLAN_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PLAN_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PLAN_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
PLAN_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PLAN_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PLAN_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PLAN_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PLAN_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PLAN_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PLAN_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PLAN_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PLAN_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PLAN_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PLAN_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
####  POC Lab Test Command 
POC Lab Test commands carry a `test_values|<label>` field per template field — those labels are determined dynamically by the selected template, so the `fields` block below shows the static shape only; dynamic per-field entries appear alongside.
POC_LAB_TEST_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "template": dict
      "indications": list[dict]
      "remarks": str
      "test_values|<label>": str   # one per template field
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
POC_LAB_TEST_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "template": dict
      "indications": list[dict]
      "remarks": str
      "test_values|<label>": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
POC_LAB_TEST_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "template": dict
      "indications": list[dict]
      "remarks": str
      "test_values|<label>": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
POC_LAB_TEST_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "template": dict
      "indications": list[dict]
      "remarks": str
      "test_values|<label>": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
POC_LAB_TEST_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "template": dict
      "indications": list[dict]
      "remarks": str
      "test_values|<label>": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
POC_LAB_TEST_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "template": dict
      "indications": list[dict]
      "remarks": str
      "test_values|<label>": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
POC_LAB_TEST_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "template": dict
      "indications": list[dict]
      "remarks": str
      "test_values|<label>": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
POC_LAB_TEST_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "template": dict
      "indications": list[dict]
      "remarks": str
      "test_values|<label>": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
POC_LAB_TEST_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "template": dict
      "indications": list[dict]
      "remarks": str
      "test_values|<label>": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
POC_LAB_TEST_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "template": dict
      "indications": list[dict]
      "remarks": str
      "test_values|<label>": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
POC_LAB_TEST_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id  
POC_LAB_TEST_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "template": dict
      "indications": list[dict]
      "remarks": str
      "test_values|<label>": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
POC_LAB_TEST_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "template": dict
      "indications": list[dict]
      "remarks": str
      "test_values|<label>": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
POC_LAB_TEST_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "template": dict
      "indications": list[dict]
      "remarks": str
      "test_values|<label>": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
POC_LAB_TEST_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "template": dict
      "indications": list[dict]
      "remarks": str
      "test_values|<label>": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
####  Prescribe Command 
PRESCRIBE_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PRESCRIBE_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PRESCRIBE_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PRESCRIBE_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
PRESCRIBE_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PRESCRIBE_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PRESCRIBE_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PRESCRIBE_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PRESCRIBE_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PRESCRIBE_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PRESCRIBE_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PRESCRIBE_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PRESCRIBE_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PRESCRIBE_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PRESCRIBE_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
PRESCRIBE__INDICATIONS__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
PRESCRIBE__INDICATIONS__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
PRESCRIBE__PHARMACY__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
PRESCRIBE__PHARMACY__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
PRESCRIBE__PRESCRIBE__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
PRESCRIBE__PRESCRIBE__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[MedicationSearchResult]  
PRESCRIBE__SUPERVISING_PROVIDER__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
PRESCRIBE__SUPERVISING_PROVIDER__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
PRESCRIBE__PRESCRIBER__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
PRESCRIBE__PRESCRIBER__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Questionnaire Command 
QUESTIONNAIRE_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
      "result": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
QUESTIONNAIRE_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
      "result": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
QUESTIONNAIRE_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
      "result": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
QUESTIONNAIRE_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
QUESTIONNAIRE_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
      "result": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
QUESTIONNAIRE_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
      "result": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
QUESTIONNAIRE_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
      "result": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
QUESTIONNAIRE_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
      "result": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
QUESTIONNAIRE_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
      "result": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
QUESTIONNAIRE_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
      "result": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
QUESTIONNAIRE_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
      "result": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
QUESTIONNAIRE_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
      "result": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
QUESTIONNAIRE_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
      "result": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
QUESTIONNAIRE_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
      "result": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
QUESTIONNAIRE_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
      "result": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
QUESTIONNAIRE__QUESTIONNAIRE__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
QUESTIONNAIRE__QUESTIONNAIRE__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Reason for Visit Command 
REASON_FOR_VISIT_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "coding": dict
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REASON_FOR_VISIT_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "coding": dict
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REASON_FOR_VISIT_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "coding": dict
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REASON_FOR_VISIT_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
REASON_FOR_VISIT_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "coding": dict
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REASON_FOR_VISIT_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "coding": dict
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REASON_FOR_VISIT_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "coding": dict
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REASON_FOR_VISIT_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "coding": dict
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REASON_FOR_VISIT_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "coding": dict
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REASON_FOR_VISIT_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "coding": dict
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REASON_FOR_VISIT_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "coding": dict
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REASON_FOR_VISIT_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "coding": dict
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REASON_FOR_VISIT_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "coding": dict
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REASON_FOR_VISIT_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "coding": dict
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REASON_FOR_VISIT_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "coding": dict
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REASON_FOR_VISIT__CODING__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
REASON_FOR_VISIT__CODING__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Refer Command 
REFER_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "refer_to": dict
      "indications": list[dict]
      "clinical_question": str
      "priority": str
      "notes_to_specialist": str
      "include_visit_note": bool
      "internal_comment": str
      "documents_to_include": dict
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFER_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "refer_to": dict
      "indications": list[dict]
      "clinical_question": str
      "priority": str
      "notes_to_specialist": str
      "include_visit_note": bool
      "internal_comment": str
      "documents_to_include": dict
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFER_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "refer_to": dict
      "indications": list[dict]
      "clinical_question": str
      "priority": str
      "notes_to_specialist": str
      "include_visit_note": bool
      "internal_comment": str
      "documents_to_include": dict
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFER_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
REFER_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "refer_to": dict
      "indications": list[dict]
      "clinical_question": str
      "priority": str
      "notes_to_specialist": str
      "include_visit_note": bool
      "internal_comment": str
      "documents_to_include": dict
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFER_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "refer_to": dict
      "indications": list[dict]
      "clinical_question": str
      "priority": str
      "notes_to_specialist": str
      "include_visit_note": bool
      "internal_comment": str
      "documents_to_include": dict
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFER_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "refer_to": dict
      "indications": list[dict]
      "clinical_question": str
      "priority": str
      "notes_to_specialist": str
      "include_visit_note": bool
      "internal_comment": str
      "documents_to_include": dict
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFER_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "refer_to": dict
      "indications": list[dict]
      "clinical_question": str
      "priority": str
      "notes_to_specialist": str
      "include_visit_note": bool
      "internal_comment": str
      "documents_to_include": dict
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFER_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "refer_to": dict
      "indications": list[dict]
      "clinical_question": str
      "priority": str
      "notes_to_specialist": str
      "include_visit_note": bool
      "internal_comment": str
      "documents_to_include": dict
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFER_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "refer_to": dict
      "indications": list[dict]
      "clinical_question": str
      "priority": str
      "notes_to_specialist": str
      "include_visit_note": bool
      "internal_comment": str
      "documents_to_include": dict
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFER_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "refer_to": dict
      "indications": list[dict]
      "clinical_question": str
      "priority": str
      "notes_to_specialist": str
      "include_visit_note": bool
      "internal_comment": str
      "documents_to_include": dict
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFER_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "refer_to": dict
      "indications": list[dict]
      "clinical_question": str
      "priority": str
      "notes_to_specialist": str
      "include_visit_note": bool
      "internal_comment": str
      "documents_to_include": dict
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFER_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "refer_to": dict
      "indications": list[dict]
      "clinical_question": str
      "priority": str
      "notes_to_specialist": str
      "include_visit_note": bool
      "internal_comment": str
      "documents_to_include": dict
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFER_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "refer_to": dict
      "indications": list[dict]
      "clinical_question": str
      "priority": str
      "notes_to_specialist": str
      "include_visit_note": bool
      "internal_comment": str
      "documents_to_include": dict
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFER_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "refer_to": dict
      "indications": list[dict]
      "clinical_question": str
      "priority": str
      "notes_to_specialist": str
      "include_visit_note": bool
      "internal_comment": str
      "documents_to_include": dict
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFER__REFER_TO__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
REFER__REFER_TO__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
REFER__INDICATIONS__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
REFER__INDICATIONS__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
REFER__DOCUMENTS_TO_INCLUDE__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
REFER__DOCUMENTS_TO_INCLUDE__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
REFER__LINKED_ITEMS__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
REFER__LINKED_ITEMS_INCLUDE__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Reference Command 
REFERENCE_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnostic_view_id": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFERENCE_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnostic_view_id": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFERENCE_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnostic_view_id": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFERENCE_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnostic_view_id": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFERENCE_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnostic_view_id": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFERENCE_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnostic_view_id": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFERENCE_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnostic_view_id": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFERENCE_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnostic_view_id": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFERENCE_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnostic_view_id": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFERENCE_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnostic_view_id": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFERENCE_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id  
REFERENCE_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnostic_view_id": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFERENCE_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnostic_view_id": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFERENCE_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnostic_view_id": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFERENCE_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "diagnostic_view_id": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
####  Referral Review Command 
REFERRAL_REVIEW_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFERRAL_REVIEW_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFERRAL_REVIEW_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFERRAL_REVIEW_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFERRAL_REVIEW_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFERRAL_REVIEW_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFERRAL_REVIEW_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFERRAL_REVIEW_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFERRAL_REVIEW_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFERRAL_REVIEW_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFERRAL_REVIEW_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFERRAL_REVIEW_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFERRAL_REVIEW__REPORT__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
REFERRAL_REVIEW__REPORT__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
REFERRAL_REVIEW__COMMUNICATION_METHOD__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
REFERRAL_REVIEW__COMMUNICATION_METHOD__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Approve Refill Command 
APPROVE_REFILL_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "refills": int
      "note_to_pharmacist": str
      "refill_request": int
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
APPROVE_REFILL_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "refills": int
      "note_to_pharmacist": str
      "refill_request": int
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
APPROVE_REFILL_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "refills": int
      "note_to_pharmacist": str
      "refill_request": int
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
APPROVE_REFILL_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "refills": int
      "note_to_pharmacist": str
      "refill_request": int
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
APPROVE_REFILL_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "refills": int
      "note_to_pharmacist": str
      "refill_request": int
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
APPROVE_REFILL_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "refills": int
      "note_to_pharmacist": str
      "refill_request": int
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
APPROVE_REFILL_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "refills": int
      "note_to_pharmacist": str
      "refill_request": int
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
APPROVE_REFILL_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "refills": int
      "note_to_pharmacist": str
      "refill_request": int
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
APPROVE_REFILL_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "refills": int
      "note_to_pharmacist": str
      "refill_request": int
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
APPROVE_REFILL_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "refills": int
      "note_to_pharmacist": str
      "refill_request": int
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
APPROVE_REFILL_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id  
APPROVE_REFILL_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "refills": int
      "note_to_pharmacist": str
      "refill_request": int
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
APPROVE_REFILL_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "refills": int
      "note_to_pharmacist": str
      "refill_request": int
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
APPROVE_REFILL_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "refills": int
      "note_to_pharmacist": str
      "refill_request": int
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
APPROVE_REFILL_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "refills": int
      "note_to_pharmacist": str
      "refill_request": int
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
####  Deny Refill Command 
DENY_REFILL_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "response_type": str
      "reason_code": str
      "note_to_pharmacist": str
      "refill_request": int
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DENY_REFILL_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "response_type": str
      "reason_code": str
      "note_to_pharmacist": str
      "refill_request": int
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DENY_REFILL_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "response_type": str
      "reason_code": str
      "note_to_pharmacist": str
      "refill_request": int
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DENY_REFILL_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "response_type": str
      "reason_code": str
      "note_to_pharmacist": str
      "refill_request": int
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DENY_REFILL_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "response_type": str
      "reason_code": str
      "note_to_pharmacist": str
      "refill_request": int
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DENY_REFILL_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "response_type": str
      "reason_code": str
      "note_to_pharmacist": str
      "refill_request": int
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DENY_REFILL_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "response_type": str
      "reason_code": str
      "note_to_pharmacist": str
      "refill_request": int
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DENY_REFILL_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "response_type": str
      "reason_code": str
      "note_to_pharmacist": str
      "refill_request": int
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DENY_REFILL_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "response_type": str
      "reason_code": str
      "note_to_pharmacist": str
      "refill_request": int
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DENY_REFILL_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "response_type": str
      "reason_code": str
      "note_to_pharmacist": str
      "refill_request": int
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DENY_REFILL_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id  
DENY_REFILL_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "response_type": str
      "reason_code": str
      "note_to_pharmacist": str
      "refill_request": int
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DENY_REFILL_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "response_type": str
      "reason_code": str
      "note_to_pharmacist": str
      "refill_request": int
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DENY_REFILL_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "response_type": str
      "reason_code": str
      "note_to_pharmacist": str
      "refill_request": int
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
DENY_REFILL_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "response_type": str
      "reason_code": str
      "note_to_pharmacist": str
      "refill_request": int
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
####  Refill Prescription Command 
REFILL_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFILL_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFILL_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFILL_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
REFILL_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFILL_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFILL_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFILL_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFILL_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFILL_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFILL_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFILL_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFILL_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFILL_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFILL_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "prescribe": dict
      "indications": list[dict]
      "sig": str
      "days_supply": int
      "quantity_to_dispense": int
      "type_to_dispense": dict
      "refills": int
      "substitutions": str
      "pharmacy": dict
      "prescriber": dict
      "note_to_pharmacist": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REFILL__INDICATIONS__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
REFILL__INDICATIONS__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
REFILL__PHARMACY__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
REFILL__PHARMACY__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
REFILL__PRESCRIBE__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[MedicationSearchResult]  
REFILL__PRESCRIBE__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
REFILL__SUPERVISING_PROVIDER__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
REFILL__SUPERVISING_PROVIDER__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
REFILL__PRESCRIBER__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
REFILL__PRESCRIBER__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Remove Allergy Command 
REMOVE_ALLERGY_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "allergy": dict
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REMOVE_ALLERGY_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "allergy": dict
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REMOVE_ALLERGY_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "allergy": dict
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REMOVE_ALLERGY_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
REMOVE_ALLERGY_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "allergy": dict
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REMOVE_ALLERGY_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "allergy": dict
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REMOVE_ALLERGY_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "allergy": dict
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REMOVE_ALLERGY_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "allergy": dict
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REMOVE_ALLERGY_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "allergy": dict
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REMOVE_ALLERGY_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "allergy": dict
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REMOVE_ALLERGY_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "allergy": dict
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REMOVE_ALLERGY_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "allergy": dict
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REMOVE_ALLERGY_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "allergy": dict
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REMOVE_ALLERGY_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "allergy": dict
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REMOVE_ALLERGY_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "allergy": dict
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
REMOVE_ALLERGY__ALLERGY__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
REMOVE_ALLERGY__ALLERGY__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Resolve Condition Command 
RESOLVE_CONDITION_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "condition": dict
      "show_in_condition_list": bool
      "rationale": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
RESOLVE_CONDITION_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "condition": dict
      "show_in_condition_list": bool
      "rationale": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
RESOLVE_CONDITION_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "condition": dict
      "show_in_condition_list": bool
      "rationale": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
RESOLVE_CONDITION_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
RESOLVE_CONDITION_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "condition": dict
      "show_in_condition_list": bool
      "rationale": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
RESOLVE_CONDITION_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "condition": dict
      "show_in_condition_list": bool
      "rationale": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
RESOLVE_CONDITION_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "condition": dict
      "show_in_condition_list": bool
      "rationale": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
RESOLVE_CONDITION_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "condition": dict
      "show_in_condition_list": bool
      "rationale": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
RESOLVE_CONDITION_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "condition": dict
      "show_in_condition_list": bool
      "rationale": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
RESOLVE_CONDITION_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "condition": dict
      "show_in_condition_list": bool
      "rationale": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
RESOLVE_CONDITION_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "condition": dict
      "show_in_condition_list": bool
      "rationale": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
RESOLVE_CONDITION_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "condition": dict
      "show_in_condition_list": bool
      "rationale": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
RESOLVE_CONDITION_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "condition": dict
      "show_in_condition_list": bool
      "rationale": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
RESOLVE_CONDITION_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "condition": dict
      "show_in_condition_list": bool
      "rationale": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
RESOLVE_CONDITION_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "condition": dict
      "show_in_condition_list": bool
      "rationale": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
RESOLVE_CONDITION__CONDITION__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
RESOLVE_CONDITION__CONDITION__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[ConditionSearchResult]  
####  Review of Systems Command 
ROS_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ROS_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ROS_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ROS_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
ROS_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ROS_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ROS_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ROS_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ROS_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ROS_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ROS_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ROS_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ROS_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ROS_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ROS_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
ROS__QUESTIONNAIRE__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
ROS__QUESTIONNAIRE__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Snooze Protocol Command 
SNOOZE_PROTOCOL_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "protocol_key": str
      "snooze_until_date": str
      "snooze_reason": str
      "snooze_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
SNOOZE_PROTOCOL_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "protocol_key": str
      "snooze_until_date": str
      "snooze_reason": str
      "snooze_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
SNOOZE_PROTOCOL_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "protocol_key": str
      "snooze_until_date": str
      "snooze_reason": str
      "snooze_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
SNOOZE_PROTOCOL_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "protocol_key": str
      "snooze_until_date": str
      "snooze_reason": str
      "snooze_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
SNOOZE_PROTOCOL_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "protocol_key": str
      "snooze_until_date": str
      "snooze_reason": str
      "snooze_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
SNOOZE_PROTOCOL_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "protocol_key": str
      "snooze_until_date": str
      "snooze_reason": str
      "snooze_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
SNOOZE_PROTOCOL_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "protocol_key": str
      "snooze_until_date": str
      "snooze_reason": str
      "snooze_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
SNOOZE_PROTOCOL_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "protocol_key": str
      "snooze_until_date": str
      "snooze_reason": str
      "snooze_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
SNOOZE_PROTOCOL_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "protocol_key": str
      "snooze_until_date": str
      "snooze_reason": str
      "snooze_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
SNOOZE_PROTOCOL_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "protocol_key": str
      "snooze_until_date": str
      "snooze_reason": str
      "snooze_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
SNOOZE_PROTOCOL_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "protocol_key": str
      "snooze_until_date": str
      "snooze_reason": str
      "snooze_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
SNOOZE_PROTOCOL_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id  
SNOOZE_PROTOCOL_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "protocol_key": str
      "snooze_until_date": str
      "snooze_reason": str
      "snooze_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
SNOOZE_PROTOCOL_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "protocol_key": str
      "snooze_until_date": str
      "snooze_reason": str
      "snooze_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
SNOOZE_PROTOCOL_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "protocol_key": str
      "snooze_until_date": str
      "snooze_reason": str
      "snooze_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
SNOOZE_PROTOCOL__PROTOCOL__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
SNOOZE_PROTOCOL__PROTOCOL__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Stop Medication Command 
STOP_MEDICATION_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "rationale": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
STOP_MEDICATION_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "rationale": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
STOP_MEDICATION_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "rationale": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
STOP_MEDICATION_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
STOP_MEDICATION_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "rationale": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
STOP_MEDICATION_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "rationale": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
STOP_MEDICATION_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "rationale": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
STOP_MEDICATION_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "rationale": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
STOP_MEDICATION_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "rationale": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
STOP_MEDICATION_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "rationale": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
STOP_MEDICATION_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "rationale": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
STOP_MEDICATION_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "rationale": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
STOP_MEDICATION_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "rationale": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
STOP_MEDICATION_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "rationale": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
STOP_MEDICATION_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "medication": dict
      "rationale": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
STOP_MEDICATION__MEDICATION__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[MedicationSearchResult]  
STOP_MEDICATION__MEDICATION__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Structured Assessment Command 
STRUCTURED_ASSESSMENT_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
STRUCTURED_ASSESSMENT_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
STRUCTURED_ASSESSMENT_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
STRUCTURED_ASSESSMENT_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
STRUCTURED_ASSESSMENT_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
STRUCTURED_ASSESSMENT_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
STRUCTURED_ASSESSMENT_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
STRUCTURED_ASSESSMENT_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
STRUCTURED_ASSESSMENT_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
STRUCTURED_ASSESSMENT_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
STRUCTURED_ASSESSMENT_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
STRUCTURED_ASSESSMENT_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
STRUCTURED_ASSESSMENT_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
STRUCTURED_ASSESSMENT_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
STRUCTURED_ASSESSMENT_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "questionnaire": dict
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
STRUCTURED_ASSESSMENT__QUESTIONNAIRE__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
STRUCTURED_ASSESSMENT__QUESTIONNAIRE__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Surgical History Command 
SURGICAL_HISTORY_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "past_surgical_history": dict
      "approximate_date":
        "input": str
        "date": str
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
SURGICAL_HISTORY_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "past_surgical_history": dict
      "approximate_date":
        "input": str
        "date": str
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
SURGICAL_HISTORY_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "past_surgical_history": dict
      "approximate_date":
        "input": str
        "date": str
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
SURGICAL_HISTORY_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
SURGICAL_HISTORY_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "past_surgical_history": dict
      "approximate_date":
        "input": str
        "date": str
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
SURGICAL_HISTORY_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "past_surgical_history": dict
      "approximate_date":
        "input": str
        "date": str
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
SURGICAL_HISTORY_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "past_surgical_history": dict
      "approximate_date":
        "input": str
        "date": str
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
SURGICAL_HISTORY_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "past_surgical_history": dict
      "approximate_date":
        "input": str
        "date": str
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
SURGICAL_HISTORY_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "past_surgical_history": dict
      "approximate_date":
        "input": str
        "date": str
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
SURGICAL_HISTORY_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "past_surgical_history": dict
      "approximate_date":
        "input": str
        "date": str
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
SURGICAL_HISTORY_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "past_surgical_history": dict
      "approximate_date":
        "input": str
        "date": str
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
SURGICAL_HISTORY_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "past_surgical_history": dict
      "approximate_date":
        "input": str
        "date": str
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
SURGICAL_HISTORY_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "past_surgical_history": dict
      "approximate_date":
        "input": str
        "date": str
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
SURGICAL_HISTORY_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "past_surgical_history": dict
      "approximate_date":
        "input": str
        "date": str
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
SURGICAL_HISTORY_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "past_surgical_history": dict
      "approximate_date":
        "input": str
        "date": str
      "comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
SURGICAL_HISTORY__PAST_SURGICAL_HISTORY__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
SURGICAL_HISTORY__PAST_SURGICAL_HISTORY__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Task Command 
TASK_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "title": str
      "assign_to": dict
      "due_date": str
      "priority": str
      "comment": str
      "labels": list[dict]
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
TASK_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "title": str
      "assign_to": dict
      "due_date": str
      "priority": str
      "comment": str
      "labels": list[dict]
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
TASK_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "title": str
      "assign_to": dict
      "due_date": str
      "priority": str
      "comment": str
      "labels": list[dict]
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
TASK_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
TASK_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "title": str
      "assign_to": dict
      "due_date": str
      "priority": str
      "comment": str
      "labels": list[dict]
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
TASK_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "title": str
      "assign_to": dict
      "due_date": str
      "priority": str
      "comment": str
      "labels": list[dict]
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
TASK_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "title": str
      "assign_to": dict
      "due_date": str
      "priority": str
      "comment": str
      "labels": list[dict]
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
TASK_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "title": str
      "assign_to": dict
      "due_date": str
      "priority": str
      "comment": str
      "labels": list[dict]
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
TASK_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "title": str
      "assign_to": dict
      "due_date": str
      "priority": str
      "comment": str
      "labels": list[dict]
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
TASK_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "title": str
      "assign_to": dict
      "due_date": str
      "priority": str
      "comment": str
      "labels": list[dict]
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
TASK_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "title": str
      "assign_to": dict
      "due_date": str
      "priority": str
      "comment": str
      "labels": list[dict]
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
TASK_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "title": str
      "assign_to": dict
      "due_date": str
      "priority": str
      "comment": str
      "labels": list[dict]
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
TASK_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "title": str
      "assign_to": dict
      "due_date": str
      "priority": str
      "comment": str
      "labels": list[dict]
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
TASK_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "title": str
      "assign_to": dict
      "due_date": str
      "priority": str
      "comment": str
      "labels": list[dict]
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
TASK_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "title": str
      "assign_to": dict
      "due_date": str
      "priority": str
      "comment": str
      "labels": list[dict]
      "linked_items": list[dict]
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
TASK__ASSIGN_TO__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
TASK__ASSIGN_TO__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
TASK__LABELS__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
TASK__LABELS__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Update Diagnosis Command 
UPDATE_DIAGNOSIS_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "conditionId": str
      "newConditionCode": str
      "background": str
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UPDATE_DIAGNOSIS_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "conditionId": str
      "newConditionCode": str
      "background": str
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UPDATE_DIAGNOSIS_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "conditionId": str
      "newConditionCode": str
      "background": str
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UPDATE_DIAGNOSIS_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "conditionId": str
      "newConditionCode": str
      "background": str
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UPDATE_DIAGNOSIS_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "conditionId": str
      "newConditionCode": str
      "background": str
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UPDATE_DIAGNOSIS_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "conditionId": str
      "newConditionCode": str
      "background": str
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UPDATE_DIAGNOSIS_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "conditionId": str
      "newConditionCode": str
      "background": str
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UPDATE_DIAGNOSIS_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "conditionId": str
      "newConditionCode": str
      "background": str
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UPDATE_DIAGNOSIS_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "conditionId": str
      "newConditionCode": str
      "background": str
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UPDATE_DIAGNOSIS_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "conditionId": str
      "newConditionCode": str
      "background": str
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UPDATE_DIAGNOSIS_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "conditionId": str
      "newConditionCode": str
      "background": str
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UPDATE_DIAGNOSIS_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id  
UPDATE_DIAGNOSIS_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "conditionId": str
      "newConditionCode": str
      "background": str
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UPDATE_DIAGNOSIS_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "conditionId": str
      "newConditionCode": str
      "background": str
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UPDATE_DIAGNOSIS_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "conditionId": str
      "newConditionCode": str
      "background": str
      "narrative": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UPDATE_DIAGNOSIS__CONDITION__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
UPDATE_DIAGNOSIS__CONDITION__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
UPDATE_DIAGNOSIS__NEW_CONDITION__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
UPDATE_DIAGNOSIS__NEW_CONDITION__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Update Goal Command 
UPDATE_GOAL_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_statement": dict
      "due_date": str
      "achievement_status": str
      "priority": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UPDATE_GOAL_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_statement": dict
      "due_date": str
      "achievement_status": str
      "priority": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UPDATE_GOAL_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_statement": dict
      "due_date": str
      "achievement_status": str
      "priority": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UPDATE_GOAL_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
UPDATE_GOAL_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_statement": dict
      "due_date": str
      "achievement_status": str
      "priority": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UPDATE_GOAL_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_statement": dict
      "due_date": str
      "achievement_status": str
      "priority": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UPDATE_GOAL_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_statement": dict
      "due_date": str
      "achievement_status": str
      "priority": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UPDATE_GOAL_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_statement": dict
      "due_date": str
      "achievement_status": str
      "priority": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UPDATE_GOAL_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_statement": dict
      "due_date": str
      "achievement_status": str
      "priority": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UPDATE_GOAL_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_statement": dict
      "due_date": str
      "achievement_status": str
      "priority": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UPDATE_GOAL_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_statement": dict
      "due_date": str
      "achievement_status": str
      "priority": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UPDATE_GOAL_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_statement": dict
      "due_date": str
      "achievement_status": str
      "priority": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UPDATE_GOAL_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_statement": dict
      "due_date": str
      "achievement_status": str
      "priority": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UPDATE_GOAL_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_statement": dict
      "due_date": str
      "achievement_status": str
      "priority": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UPDATE_GOAL_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "goal_statement": dict
      "due_date": str
      "achievement_status": str
      "priority": str
      "progress": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UPDATE_GOAL__GOAL_STATEMENT__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
UPDATE_GOAL__GOAL_STATEMENT__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Uncategorized Document Review Command 
UNCATEGORIZED_DOCUMENT_REVIEW_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UNCATEGORIZED_DOCUMENT_REVIEW_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UNCATEGORIZED_DOCUMENT_REVIEW_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UNCATEGORIZED_DOCUMENT_REVIEW_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UNCATEGORIZED_DOCUMENT_REVIEW_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UNCATEGORIZED_DOCUMENT_REVIEW_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UNCATEGORIZED_DOCUMENT_REVIEW_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UNCATEGORIZED_DOCUMENT_REVIEW_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UNCATEGORIZED_DOCUMENT_REVIEW_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UNCATEGORIZED_DOCUMENT_REVIEW_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UNCATEGORIZED_DOCUMENT_REVIEW_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UNCATEGORIZED_DOCUMENT_REVIEW_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "report": str
      "message_to_patient": str
      "communication_method": str
      "internal_comment": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
UNCATEGORIZED_DOCUMENT_REVIEW__REPORT__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
UNCATEGORIZED_DOCUMENT_REVIEW__REPORT__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
UNCATEGORIZED_DOCUMENT_REVIEW__COMMUNICATION_METHOD__PRE_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
UNCATEGORIZED_DOCUMENT_REVIEW__COMMUNICATION_METHOD__POST_SEARCH  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "search_term": str
    "user": {
      "staff": staff_key
    }
    "results": list[dict]  
####  Visual Exam Finding 
VISUAL_EXAM_FINDING_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "title": str
      "narrative": str
      "image": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VISUAL_EXAM_FINDING_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "title": str
      "narrative": str
      "image": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VISUAL_EXAM_FINDING_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "title": str
      "narrative": str
      "image": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VISUAL_EXAM_FINDING_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "title": str
      "narrative": str
      "image": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VISUAL_EXAM_FINDING_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "title": str
      "narrative": str
      "image": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VISUAL_EXAM_FINDING_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "title": str
      "narrative": str
      "image": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VISUAL_EXAM_FINDING_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "title": str
      "narrative": str
      "image": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VISUAL_EXAM_FINDING_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "title": str
      "narrative": str
      "image": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VISUAL_EXAM_FINDING_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "title": str
      "narrative": str
      "image": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VISUAL_EXAM_FINDING_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "title": str
      "narrative": str
      "image": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VISUAL_EXAM_FINDING_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id  
VISUAL_EXAM_FINDING_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "title": str
      "narrative": str
      "image": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VISUAL_EXAM_FINDING_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "title": str
      "narrative": str
      "image": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VISUAL_EXAM_FINDING_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "title": str
      "narrative": str
      "image": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VISUAL_EXAM_FINDING_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "title": str
      "narrative": str
      "image": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
####  Vitals Command 
VITALS_COMMAND__POST_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "height": str
      "weight_lbs": str
      "weight_oz": str
      "waist_circumference": str
      "body_temperature": str
      "body_temperature_site": str
      "blood_pressure_systole": int
      "blood_pressure_diastole": str
      "blood_pressure_position_and_site": str
      "pulse": str
      "pulse_rhythm": str
      "respiration_rate": int
      "oxygen saturation": str
      "note": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VITALS_COMMAND__POST_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "height": str
      "weight_lbs": str
      "weight_oz": str
      "waist_circumference": str
      "body_temperature": str
      "body_temperature_site": str
      "blood_pressure_systole": int
      "blood_pressure_diastole": str
      "blood_pressure_position_and_site": str
      "pulse": str
      "pulse_rhythm": str
      "respiration_rate": int
      "oxygen saturation": str
      "note": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VITALS_COMMAND__POST_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "height": str
      "weight_lbs": str
      "weight_oz": str
      "waist_circumference": str
      "body_temperature": str
      "body_temperature_site": str
      "blood_pressure_systole": int
      "blood_pressure_diastole": str
      "blood_pressure_position_and_site": str
      "pulse": str
      "pulse_rhythm": str
      "respiration_rate": int
      "oxygen saturation": str
      "note": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VITALS_COMMAND__AVAILABLE_ACTIONS  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "actions":
      "name": string
    "user":
      "staff": staff_id
VITALS_COMMAND__POST_VALIDATION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "height": str
      "weight_lbs": str
      "weight_oz": str
      "waist_circumference": str
      "body_temperature": str
      "body_temperature_site": str
      "blood_pressure_systole": int
      "blood_pressure_diastole": str
      "blood_pressure_position_and_site": str
      "pulse": str
      "pulse_rhythm": str
      "respiration_rate": int
      "oxygen saturation": str
      "note": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VITALS_COMMAND__POST_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "height": str
      "weight_lbs": str
      "weight_oz": str
      "waist_circumference": str
      "body_temperature": str
      "body_temperature_site": str
      "blood_pressure_systole": int
      "blood_pressure_diastole": str
      "blood_pressure_position_and_site": str
      "pulse": str
      "pulse_rhythm": str
      "respiration_rate": int
      "oxygen saturation": str
      "note": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VITALS_COMMAND__POST_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "height": str
      "weight_lbs": str
      "weight_oz": str
      "waist_circumference": str
      "body_temperature": str
      "body_temperature_site": str
      "blood_pressure_systole": int
      "blood_pressure_diastole": str
      "blood_pressure_position_and_site": str
      "pulse": str
      "pulse_rhythm": str
      "respiration_rate": int
      "oxygen saturation": str
      "note": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VITALS_COMMAND__POST_INSERTED_INTO_NOTE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "height": str
      "weight_lbs": str
      "weight_oz": str
      "waist_circumference": str
      "body_temperature": str
      "body_temperature_site": str
      "blood_pressure_systole": int
      "blood_pressure_diastole": str
      "blood_pressure_position_and_site": str
      "pulse": str
      "pulse_rhythm": str
      "respiration_rate": int
      "oxygen saturation": str
      "note": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VITALS_COMMAND__POST_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "height": str
      "weight_lbs": str
      "weight_oz": str
      "waist_circumference": str
      "body_temperature": str
      "body_temperature_site": str
      "blood_pressure_systole": int
      "blood_pressure_diastole": str
      "blood_pressure_position_and_site": str
      "pulse": str
      "pulse_rhythm": str
      "respiration_rate": int
      "oxygen saturation": str
      "note": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VITALS_COMMAND__PRE_COMMIT  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "height": str
      "weight_lbs": str
      "weight_oz": str
      "waist_circumference": str
      "body_temperature": str
      "body_temperature_site": str
      "blood_pressure_systole": int
      "blood_pressure_diastole": str
      "blood_pressure_position_and_site": str
      "pulse": str
      "pulse_rhythm": str
      "respiration_rate": int
      "oxygen saturation": str
      "note": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VITALS_COMMAND__PRE_DELETE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "height": str
      "weight_lbs": str
      "weight_oz": str
      "waist_circumference": str
      "body_temperature": str
      "body_temperature_site": str
      "blood_pressure_systole": int
      "blood_pressure_diastole": str
      "blood_pressure_position_and_site": str
      "pulse": str
      "pulse_rhythm": str
      "respiration_rate": int
      "oxygen saturation": str
      "note": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VITALS_COMMAND__PRE_ENTER_IN_ERROR  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "height": str
      "weight_lbs": str
      "weight_oz": str
      "waist_circumference": str
      "body_temperature": str
      "body_temperature_site": str
      "blood_pressure_systole": int
      "blood_pressure_diastole": str
      "blood_pressure_position_and_site": str
      "pulse": str
      "pulse_rhythm": str
      "respiration_rate": int
      "oxygen saturation": str
      "note": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VITALS_COMMAND__PRE_EXECUTE_ACTION  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "height": str
      "weight_lbs": str
      "weight_oz": str
      "waist_circumference": str
      "body_temperature": str
      "body_temperature_site": str
      "blood_pressure_systole": int
      "blood_pressure_diastole": str
      "blood_pressure_position_and_site": str
      "pulse": str
      "pulse_rhythm": str
      "respiration_rate": int
      "oxygen saturation": str
      "note": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VITALS_COMMAND__PRE_ORIGINATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "height": str
      "weight_lbs": str
      "weight_oz": str
      "waist_circumference": str
      "body_temperature": str
      "body_temperature_site": str
      "blood_pressure_systole": int
      "blood_pressure_diastole": str
      "blood_pressure_position_and_site": str
      "pulse": str
      "pulse_rhythm": str
      "respiration_rate": int
      "oxygen saturation": str
      "note": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
VITALS_COMMAND__PRE_UPDATE  
---  
Target object | Context object  
    "id": command_uuid
    "type": [Command](/sdk/data-command/)
| 
    "fields":
      "height": str
      "weight_lbs": str
      "weight_oz": str
      "waist_circumference": str
      "body_temperature": str
      "body_temperature_site": str
      "blood_pressure_systole": int
      "blood_pressure_diastole": str
      "blood_pressure_position_and_site": str
      "pulse": str
      "pulse_rhythm": str
      "respiration_rate": int
      "oxygen saturation": str
      "note": str
    "note":
      "uuid": note_id
    "patient":
      "id": pt_id  
###  Patient Portal Events 
PATIENT_PORTAL__APPOINTMENT_CANCELED  
---  
Occurs after an appointment is canceled  
Target | Target type | Context object  
    appt_id
| 
    [Appointment](/sdk/data-appointment/)
| 
    None  
PATIENT_PORTAL__APPOINTMENT_RESCHEDULED  
---  
Occurs after an appointment is rescheduled  
Target | Target type | Context object  
    appt_id
| 
    [Appointment](/sdk/data-appointment/)
| 
    None  
PATIENT_PORTAL__APPOINTMENT_CAN_BE_CANCELED  
---  
Occurs when checking if an appointment can be canceled  
Target | Target type | Context object  
    appt_id
| 
    [Appointment](/sdk/data-appointment/)
| 
    None  
PATIENT_PORTAL__APPOINTMENT_CAN_BE_RESCHEDULED  
---  
Occurs when checking if an appointment can be rescheduled  
Target | Target type | Context object  
    appt_id
| 
    [Appointment](/sdk/data-appointment/)
| 
    None  
PATIENT_PORTAL__APPOINTMENTS__SLOTS__POST_SEARCH  
---  
Occurs after the appointment slots search has been done, allowing the values to be modified  
Target | Target type | Context object  
    patient_id
| 
    [Patient](/sdk/data-patient/)
| 
    "slots_by_provider": {provider: {date: [{"start", "end"}]}} (JSON string)  
PATIENT_PORTAL__APPOINTMENTS__FORM_APPOINTMENT_TYPES__PRE_SEARCH  
---  
Occurs before appointment types are resolved, allowing the internal values to be bypassed  
Target | Target type | Context object  
    patient_id
| 
    [Patient](/sdk/data-patient/)
| 
    None  
PATIENT_PORTAL__APPOINTMENTS__FORM_APPOINTMENT_TYPES__POST_SEARCH  
---  
Occurs after appointment types are resolved, allowing the internal values to be modified  
Target | Target type | Context object  
    patient_id
| 
    [Patient](/sdk/data-patient/)
| 
    "appointment_types": [{"id", "title"}]  
PATIENT_PORTAL__APPOINTMENTS__FORM_LOCATIONS__PRE_SEARCH  
---  
Occurs before appointment locations are resolved, allowing the internal values to be bypassed  
Target | Target type | Context object  
    patient_id
| 
    [Patient](/sdk/data-patient/)
| 
    None  
PATIENT_PORTAL__APPOINTMENTS__FORM_LOCATIONS__POST_SEARCH  
---  
Occurs after appointment locations are resolved, allowing the internal values to be modified  
Target | Target type | Context object  
    patient_id
| 
    [Patient](/sdk/data-patient/)
| 
    "locations": [{"id", "title"}]  
PATIENT_PORTAL__APPOINTMENTS__FORM_PROVIDERS__PRE_SEARCH  
---  
Occurs before appointment providers are resolved, allowing the internal values to be bypassed  
Target | Target type | Context object  
    patient_id
| 
    [Patient](/sdk/data-patient/)
| 
    None  
PATIENT_PORTAL__APPOINTMENTS__FORM_PROVIDERS__POST_SEARCH  
---  
Occurs after appointment providers are resolved, allowing the internal values to be modified  
Target | Target type | Context object  
    patient_id
| 
    [Patient](/sdk/data-patient/)
| 
    "providers": [{"id", "title"}]  
PATIENT_PORTAL__GET_FORMS  
---  
Occurs on every page load of the Patient Portal; It only accepts the `PATIENT_PORTAL__FORM_RESULT` effect as a return value  
Target | Target type | Context object  
    patient_id
| 
    [Patient](/sdk/data-patient/)
| 
    "requested_from": str["appointment" |
                          "labs" |
                          "login" |
                          "messaging" |
                          "my-health" |
                          "payment" |
                          "search-appointment"]  
###  Action Buttons Events 
For more information on handling these events, see [Action Buttons](/sdk/handlers-action-buttons).
SHOW_NOTE_HEADER_BUTTON  
---  
Occurs when patient notes are being loaded  
Target | Context object  
    patient_id
| 
      "note_id": str
      "user":
        "id": str
        "type": [Staff](/sdk/data-staff/)  
SHOW_NOTE_FOOTER_BUTTON  
---  
Occurs when patient notes are being loaded  
Target | Context object  
    patient_id
| 
      "note_id": str
      "user":
        "id": str
        "type": [Staff](/sdk/data-staff/)  
SHOW_NOTE_BODY_BUTTON  
---  
Occurs when patient notes are being loaded  
Target | Context object  
    patient_id
| 
      "note_id": str
      "user":
        "id": str
        "type": [Staff](/sdk/data-staff/)  
SHOW_NOTE_BODY_AUTOMATION_BUTTON  
---  
Occurs when patient notes are being loaded  
Target | Context object  
    patient_id
| 
      "note_id": str
      "user":
        "id": str
        "type": [Staff](/sdk/data-staff/)  
SHOW_NOTE_HEADER_DROPDOWN_BUTTON  
---  
Occurs when patient notes are being loaded  
Target | Context object  
    patient_id
| 
      "note_id": str
      "user":
        "id": str
        "type": [Staff](/sdk/data-staff/)  
SHOW_CHART_SUMMARY_SOCIAL_DETERMINANTS_SECTION_BUTTON  
---  
Occurs when patient chart summary is being loaded, specifically for social determinants section  
Target | Target type | Context object  
    patient_id
| 
    [Patient](/sdk/data-patient/)
| 
      "user":
        "id": str
        "type": [Staff](/sdk/data-staff/) | [Patient](/sdk/data-patient/)  
SHOW_CHART_SUMMARY_GOALS_SECTION_BUTTON  
---  
Occurs when patient chart summary is being loaded, specifically for goals section  
Target | Target type | Context object  
    patient_id
| 
    [Patient](/sdk/data-patient/)
| 
      "user":
        "id": str
        "type": [Staff](/sdk/data-staff/) | [Patient](/sdk/data-patient/)  
SHOW_CHART_SUMMARY_CONDITIONS_SECTION_BUTTON  
---  
Occurs when patient chart summary is being loaded, specifically for conditions section  
Target | Target type | Context object  
    patient_id
| 
    [Patient](/sdk/data-patient/)
| 
      "user":
        "id": str
        "type": [Staff](/sdk/data-staff/) | [Patient](/sdk/data-patient/)  
SHOW_CHART_SUMMARY_MEDICATIONS_SECTION_BUTTON  
---  
Occurs when patient chart summary is being loaded, specifically for medications section  
Target | Target type | Context object  
    patient_id
| 
    [Patient](/sdk/data-patient/)
| 
      "user":
        "id": str
        "type": [Staff](/sdk/data-staff/) | [Patient](/sdk/data-patient/)  
SHOW_CHART_SUMMARY_ALLERGIES_SECTION_BUTTON  
---  
Occurs when patient chart summary is being loaded, specifically for allergies section  
Target | Target type | Context object  
    patient_id
| 
    [Patient](/sdk/data-patient/)
| 
      "user":
        "id": str
        "type": [Staff](/sdk/data-staff/) | [Patient](/sdk/data-patient/)  
SHOW_CHART_SUMMARY_CARE_TEAMS_SECTION_BUTTON  
---  
Occurs when patient chart summary is being loaded, specifically for care teams section  
Target | Target type | Context object  
    patient_id
| 
    [Patient](/sdk/data-patient/)
| 
      "user":
        "id": str
        "type": [Staff](/sdk/data-staff/) | [Patient](/sdk/data-patient/)  
SHOW_CHART_SUMMARY_VITALS_SECTION_BUTTON  
---  
Occurs when patient chart summary is being loaded, specifically for vitals section  
Target | Target type | Context object  
    patient_id
| 
    [Patient](/sdk/data-patient/)
| 
      "user":
        "id": str
        "type": [Staff](/sdk/data-staff/) | [Patient](/sdk/data-patient/)  
SHOW_CHART_SUMMARY_IMMUNIZATIONS_SECTION_BUTTON  
---  
Occurs when patient chart summary is being loaded, specifically for immunizations section  
Target | Target type | Context object  
    patient_id
| 
    [Patient](/sdk/data-patient/)
| 
      "user":
        "id": str
        "type": [Staff](/sdk/data-staff/) | [Patient](/sdk/data-patient/)  
SHOW_CHART_SUMMARY_SURGICAL_HISTORY_SECTION_BUTTON  
---  
Occurs when patient chart summary is being loaded, specifically for surgical history section  
Target | Target type | Context object  
    patient_id
| 
    [Patient](/sdk/data-patient/)
| 
      "user":
        "id": str
        "type": [Staff](/sdk/data-staff/) | [Patient](/sdk/data-patient/)  
SHOW_CHART_SUMMARY_FAMILY_HISTORY_SECTION_BUTTON  
---  
Occurs when patient chart summary is being loaded, specifically for family history section  
Target | Target type | Context object  
    patient_id
| 
    [Patient](/sdk/data-patient/)
| 
      "user":
        "id": str
        "type": [Staff](/sdk/data-staff/) | [Patient](/sdk/data-patient/)  
SHOW_CHART_SUMMARY_CODING_GAPS_SECTION_BUTTON  
---  
Occurs when patient chart summary is being loaded, specifically for coding gaps section  
Target | Target type | Context object  
    patient_id
| 
    [Patient](/sdk/data-patient/)
| 
      "user":
        "id": str
        "type": [Staff](/sdk/data-staff/) | [Patient](/sdk/data-patient/)  
ACTION_BUTTON_CLICKED  
---  
Occurs when an action button is clicked  
Target | Context object  
    patient_id
| 
      "key": action_button_key
      "user":
        "id": str
        "type": [Staff](/sdk/data-staff/) | [Patient](/sdk/data-patient/)  
###  Note Footer Configuration 
NOTE_FOOTER__GET_CONFIGURATION  
---  
Occurs when a note's footer is loaded. Allows plugins to configure the footer — for example, to hide Canvas's default state-transition buttons so plugin-provided buttons replace them. See the [Note Footer Configuration effect](/sdk/effect-note-footer-configuration/) for usage details.  
Target | Context object  
    note_id
| 
    empty  
###  Application Events 
For more information on these events, see [Applications](/sdk/handlers-applications).
APPLICATION__ON_GET  
---  
Occurs when Canvas requests the available applications for a given scope. Handled automatically by [Note Applications](/sdk/handlers-embedded-applications/#note-applications) to return application metadata via the `SHOW_APPLICATION` effect.  
Target | Context object  
    scope
| 
      "scope": str
      "patient":
        "id": str
      "user":
        "id": str
        "type": [Staff](/sdk/data-staff/) | [Patient](/sdk/data-patient/)  
APPLICATION__ON_OPEN  
---  
Occurs when a user clicks on an application icon to open it  
Target | Context object  
    application_id
| 
      "patient":
        "id": str
      "user":
        "id": str
        "type": [Staff](/sdk/data-staff/) | [Patient](/sdk/data-patient/)  
###  Payment Processor Events 
These events drive the custom [Payment Processor](/sdk/handlers-payment-processors/) handlers. They are dispatched as a user moves through a payment workflow, and the actor that initiated the workflow is set on each event (see Event Actor). Every event other than `REVENUE__PAYMENT_PROCESSOR__LIST` includes the `identifier` of the processor it targets, so a handler only acts when the identifier matches its own.
The `identifier` is the unique id of the payment processor handler the event is intended for. Canvas derives it from the handler's class (its module path and class name) and exposes it on the handler as `self.identifier`; it is the same value the handler advertises via the [`PaymentProcessorMetadata`](/sdk/payment-processor-effect/#paymentprocessormetadata) effect when responding to `REVENUE__PAYMENT_PROCESSOR__LIST`.
REVENUE__PAYMENT_PROCESSOR__LIST  
---  
Occurs when Canvas gathers the list of available payment processors. A handler responds with a [PaymentProcessorMetadata](/sdk/payment-processor-effect/#paymentprocessormetadata) effect.  
Target | Context object  
    None
| 
    "payment_type": str  # optional, e.g. "card"  
REVENUE__PAYMENT_PROCESSOR__SELECTED  
---  
Occurs when a payment processor is selected. A handler responds with one or more [PaymentProcessorForm](/sdk/payment-processor-effect/#paymentprocessorform) effects.  
Target | Context object  
    None
| 
    "identifier": str
    "intent": str  # optional, "pay" | "add_card"
    "patient":
        "id": str  # optional  
REVENUE__PAYMENT_PROCESSOR__CHARGE  
---  
Occurs when a card is charged. A handler responds with a [CardTransaction](/sdk/payment-processor-effect/#cardtransaction) effect.  
Target | Context object  
    None
| 
    "identifier": str
    "amount": str
    "token": str
    "additional_context": str  # optional
    "patient":
        "id": str  # optional  
REVENUE__PAYMENT_PROCESSOR__PAYMENT_METHODS__LIST  
---  
Occurs when Canvas lists a patient's saved payment methods. A handler responds with one or more [PaymentMethod](/sdk/payment-processor-effect/#paymentmethod) effects.  
Target | Context object  
    None
| 
    "identifier": str
    "patient":
        "id": str  # optional  
REVENUE__PAYMENT_PROCESSOR__PAYMENT_METHODS__ADD  
---  
Occurs when a payment method is added for a patient. A handler responds with an [AddPaymentMethodResponse](/sdk/payment-processor-effect/#addpaymentmethodresponse) effect.  
Target | Context object  
    None
| 
    "identifier": str
    "token": str
    "additional_context": str  # optional
    "patient":
        "id": str  
REVENUE__PAYMENT_PROCESSOR__PAYMENT_METHODS__REMOVE  
---  
Occurs when a payment method is removed for a patient. A handler responds with a [RemovePaymentMethodResponse](/sdk/payment-processor-effect/#removepaymentmethodresponse) effect.  
Target | Context object  
    None
| 
    "identifier": str
    "token": str
    "patient":
        "id": str  
###  Patient Portal Events 
APPLICATION__ON_CONTEXT_CHANGE  
---  
Occurs when a user navigates to a different URL while an application is open. Currently supported for revenue workflows.  
Target | Context object  
    application_id
| 
      "url": str
      "patient":
        "id": str
      "user":
        "id": str
        "type": [Staff](/sdk/data-staff/) | [Patient](/sdk/data-patient/)
      "claim": (optional)
        "id": str
      "claim_queue": (optional)
        "dbid": str  
APPLICATION__GET_NOTIFICATION_BADGE  
---  
Occurs when Canvas loads applications and requests the current notification badge count. Respond by overriding `compute_notification_badge()` on your [Application](/sdk/handlers-applications/#notification-badges) handler.  
Target | Context object  
    application_id
| 
      "staff":
        "id": str
        "type": [Staff](/sdk/data-staff/)
      "patient":
        "id": str
        "type": [Patient](/sdk/data-patient/)  
###  Patient Chart Configuration 
PATIENT_CHART__CONDITIONS  
---  
Occurs when the conditions are loaded on the patient chart.  
Target object | Context object  
    "id": patient_id
    "type": [Patient](/sdk/data-patient/)
| 
    "conditions":
        "id": condition id
        "codings":
          "code": str
          "system": str
          "display": str  
PATIENT_CHART__MEDICATIONS  
---  
Occurs when the medications are loaded on the patient chart.  
Target object | Context object  
    "id": patient_id
    "type": [Patient](/sdk/data-patient/)
| 
    "medications":
        "id": medication id
        "codings":
          "code": str
          "system": str
          "display": str  
###  Patient Timeline Configuration 
PATIENT_TIMELINE__GET_CONFIGURATION  
---  
Occurs when a patient's chart is loaded. Allows plugins to configure which note types are visible on the timeline, and which the New Note button may offer. See the [Patient Timeline effect](/sdk/effect-patient-timeline/) for usage details.  
Target object | Context object  
    "id": patient_key
    "type": [Patient](/sdk/data-patient/)
| 
    empty  
###  Patient Group 
PATIENT_GROUP_CREATED  
---  
Occurs when a patient group is created  
Target object | Context object  
    "id": patient_group_id
    "type": [Patient Group](/sdk/data-patient-group/)
| 
    empty  
PATIENT_GROUP_UPDATED  
---  
Occurs when a patient group is updated  
Target object | Context object  
    "id": patient_group_id
    "type": [Patient Group](/sdk/data-patient-group/)
| 
    empty  
PATIENT_GROUP_MEMBERSHIP_CREATED  
---  
Occurs when a patient is added as a member of a group  
Target object | Context object  
    "id": patient_group_id
    "type": [Patient Group](/sdk/data-patient-group/)
| 
    "patient":
            "id": str
          "group": 
            "id": str
PATIENT_GROUP_MEMBERSHIP_UPDATED  
---  
Occurs when a patient's group membership is updated  
Target object | Context object  
    "id": patient_group_id
    "type": [Patient Group](/sdk/data-patient-group/)
| 
    "patient":
            "id": str
          "group": 
            "id": str
PATIENT_GROUP_MEMBERSHIP_DELETED  
---  
Occurs when a patient member is removed from a patient group  
Target object | Context object  
    "id": patient_group_id
    "type": [Patient Group](/sdk/data-patient-group/)
| 
    "patient":
            "id": str
          "group": 
            "id": str  
###  SSO Events 
For more information on these events, see [SSO Capabilities](/sdk/sso/).
Event | Occurs when | SSO__PROCESS_ADDITIONAL_REQUEST_DATA | A user has just authenticated via SAML SSO. Read-only access to the SAML response. See [SSO Capabilities](/sdk/sso/#sso__process_additional_request_data).  
---|---  
SSO__GET_POST_LOGIN_REDIRECT | A user has just authenticated via SAML SSO and Canvas is deciding where to send them. Return a [REDIRECT_CONTEXT](/sdk/effect-redirect/) effect to override the destination. See [SSO Capabilities](/sdk/sso/#sso__get_post_login_redirect).  
###  Other Events 
Event | Occurs when | UNKNOWN | Default event type unlikely to ever be emitted.  
---|---  
CRON | This event fires regularly and can be used for scheduled tasks. See [CronTask](/sdk/handlers-crontask/).  
PATIENT_CHART_SUMMARY__SECTION_CONFIGURATION | A patient chart's summary section is loading.  
PATIENT_CHART_SUMMARY__GET_CUSTOM_SECTION | Canvas is requesting the content for a custom patient chart summary section. See [Patient Chart Summary Custom Section Handler](/sdk/patient-chart-summary-custom-section-handler/).  
PANEL_SECTIONS_CONFIGURATION | The panel section is loading.  
GET_PROVIDER_MENU_CONFIGURATION | The provider (hamburger) menu is loading. See [Provider Menu Configuration](/sdk/layout-effect/#provider-menu-configuration).  
PLUGIN_CREATED | A plugin is uploaded for the first time. See [ProtocolCards](/sdk/effect-protocol-cards/) and [BannerAlerts](/sdk/effect-banner-alerts/) for examples of how to use this event.  
PLUGIN_UPDATED | A plugin is enabled or when the plugin code has changed. See [ProtocolCards](/sdk/effect-protocol-cards/) and [BannerAlerts](/sdk/effect-banner-alerts/) for examples of how to use this event.  
PATIENT_PROFILE__ADD_PHARMACY__POST_SEARCH_RESULTS | Adding a pharmacy for a patient in their profile.  
FAX__RECIPIENT__PRE_SEARCH | Searching for a fax recipient, before the search runs. Reply with `AUTOCOMPLETE_SEARCH_RESULTS` to supply the results; returning nothing runs the normal search. See [Service Provider search results](/sdk/data-serviceprovider/#search-results).   
Context object: 
    "search_term": str
    "source": "fax"
    "results": list[dict]
    "user":
        "staff": str  
FAX__RECIPIENT__POST_SEARCH | Searching for a fax recipient, after the search runs. Reply with `AUTOCOMPLETE_SEARCH_RESULTS` to replace or annotate the results.   
Context object: 
    "search_term": str
    "source": "fax"
    "results": list[dict]
    "user":
        "staff": str  
PATIENT_PROFILE__EXTERNAL_CARE_TEAM__PRE_SEARCH | Searching for a provider to add to a patient's external care team, before the search runs. Reply with `AUTOCOMPLETE_SEARCH_RESULTS` to supply the results; returning nothing runs the normal search.   
Context object: 
    "search_term": str
    "source": "care_team"
    "results": list[dict]
    "user":
        "staff": str  
PATIENT_PROFILE__EXTERNAL_CARE_TEAM__POST_SEARCH | Searching for a provider to add to a patient's external care team, after the search runs. Reply with `AUTOCOMPLETE_SEARCH_RESULTS` to replace or annotate the results.   
Context object: 
    "search_term": str
    "source": "care_team"
    "results": list[dict]
    "user":
        "staff": str  
PATIENT_PORTAL__WIDGET_CONFIGURATION | Patient Portal landing page is loading. See [Tailoring Portal Landing Page](/guides/custom-landing-page/) for examples of how to use this event.  
PATIENT_METADATA__GET_ADDITIONAL_FIELDS | Patient Profile is loading. See [How to add patient profile additional fields](/guides/profile-additional-fields/) for examples of how to use this event.   
Context object: 
    "patient":
        "id": str
    "user":
        "id": str
        "type": [Staff](/sdk/data-staff/) | [Patient](/sdk/data-patient/)  
GET_HOMEPAGE_CONFIGURATION | Homepage is loading. See [Set default homepage](/guides/set-default-homepage/) for examples of how to use this event.  
COMMAND__FORM__GET_ADDITIONAL_FIELDS | Command is originated. See [Command metadata Create Form](/sdk/command-metadata-create-form-effect/) for how to use this event.   
Target: 
    "command_uuid": str
Context object: 
    "schema_key": str
    "purpose": "form" | "print"  
###  Search Result Data Structures 
Many event payloads include search results. This section documents the common structures within them.
####  MedicationSearchResult 
Medication search events (such as `MEDICATION_STATEMENT__MEDICATION__POST_SEARCH`, `PRESCRIBE__PRESCRIBE__POST_SEARCH`, etc.) return results that follow this structure:
    ```json
    {
      "text": "acetaminophen 500 mg tablet",
      "disabled": false,
      "description": null,
      "annotations": null,
      "extra": {
        "coding": [
          {
            "code": 206813,
            "display": "acetaminophen 500 mg tablet",
            "system": "http://www.fdbhealth.com/"
          },
          {
            "code": "198440",
            "display": "acetaminophen 500 mg tablet",
            "system": "http://www.nlm.nih.gov/research/umls/rxnorm"
          }
        ],
        "clinical_quantities": [
          {
            "erx_quantity": "1.0000000",
            "representative_ndc": "57896021910",
            "clinical_quantity_description": "tablet",
            "erx_ncpdp_script_quantity_qualifier_code": "C48542",
            "erx_ncpdp_script_quantity_qualifier_description": "Tablet"
          }
        ]
      },
      "value": 206813
    }
    ```
For detailed information about medication data structures, see [Medication](/sdk/data-medication/).
For examples of working with medication search results, see the [Customize Search Results](/guides/customize-search-results/) guide.
####  ConditionSearchResult 
Condition/diagnosis search events (such as `DIAGNOSE__DIAGNOSE__POST_SEARCH`, `MEDICAL_HISTORY__PAST_MEDICAL_HISTORY__POST_SEARCH`, etc.) return results that follow this structure:
    ```json
    {
      "text": "Broken internal left hip prosthesis, subsequent encounter",
      "disabled": false,
      "description": null,
      "annotations": ["T84.011D"],
      "extra": {
        "coding": [
          {
            "code": "T84011D",
            "display": "Broken internal left hip prosthesis, subsequent encounter",
            "system": "ICD-10"
          },
          {
            "code": 404684003,
            "display": "Broken internal left hip prosthesis, subsequent encounter",
            "system": "http://snomed.info/sct"
          }
        ]
      },
      "value": "T84011D"
    }
    ```
For detailed information about condition data structures, see [Condition](/sdk/data-condition/).
####  AllergySearchResult 
Allergy search events (such as `ALLERGY__ALLERGY__POST_SEARCH`, `REMOVE_ALLERGY__ALLERGY__POST_SEARCH`, etc.) return results that follow this structure:
    ```json
    {
      "text": "Penicillins (allergy group)",
      "disabled": false,
      "description": null,
      "annotations": null,
      "extra": {
        "coding": [
          {
            "code": 476,
            "display": "Penicillins",
            "system": "http://www.fdbhealth.com/"
          }
        ],
        "category_id": 1,
        "category": "allergy group"
      },
      "value": 476
    }
    ```
For detailed information about allergy data structures, see [Allergy Intolerance](/sdk/data-allergy-intolerance/).
----- END PAGE https://docs.canvasmedical.com/sdk/events/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/example-abnormal_lab_task_notification/
> **Note:** [View the source](https://github.com/canvas-medical/canvas-plugins/tree/main/example-plugins/abnormal_lab_task_notification) for this plugin on GitHub. 
This Canvas EMR plugin automatically creates task notifications whenever lab results with abnormal values are received, ensuring critical lab findings are flagged for prompt clinical review.
##  SDK Features 
  - Responds to `LAB_REPORT_CREATED` [event](/sdk/events/#labs)
  - Loads and parses the [Lab Report data model](/sdk/data-labs/#labreport) to identify [lab values](/sdk/data-labs/#labvalue) flagged as abnormal
  - Returns a [task effect](/sdk/effect-tasks/#adding-a-task)
##  CANVAS_MANIFEST.json 
    ```json
    {
        "sdk_version": "0.1.4",
        "plugin_version": "0.1.0",
        "name": "abnormal_lab_task_notification",
        "description": "A plugin that creates task notifications for abnormal lab values",
        "components": {
            "handlers": [
                {
                    "class": "abnormal_lab_task_notification.handlers.abnormal_lab_handler:AbnormalLabHandler",
                    "description": "Monitors lab reports and creates tasks for abnormal values",
                    "data_access": {
                        "event": "LAB_REPORT_CREATED",
                        "read": [
                            "lab_reports",
                            "lab_values"
                        ],
                        "write": [
                            "tasks"
                        ]
                    }
                }
            ],
            "commands": [],
            "content": [],
            "effects": [],
            "views": []
        },
        "tags": ["lab", "notifications", "tasks"],
        "variables": [],
        "license": "NONE",
        "readme": "This plugin monitors incoming lab reports and creates task notifications for any abnormal lab values, ensuring they are flagged for prompt review."
    }
    ```
##  handlers/ 
###  abnormal_lab_handler.py 
**Purpose and Functionality**
This file defines a handler called AbnormalLabHandler for use with the Canvas Medical SDK. Its primary function is to monitor for the creation of new laboratory reports (`LAB_REPORT_CREATED` events). When such an event occurs, the handler inspects the report to determine if it contains any abnormal lab values. If abnormal results are found, it automatically creates a task for prompt clinical review.
**Event Handling**
  - The handler listens for the LAB_REPORT_CREATED event.
  - When triggered, it examines the relevant [lab report(/sdk/data-labs/#labreport)] for any values marked as abnormal.
**Core Logic**
  - It fetches the full LabReport instance specified by the event.
  - It filters out any reports that are for test purposes, junked, or do not belong to a patient.
  - It iterates through all values within the lab report.
  - For each value, it checks if there is a non-empty abnormal_flag.
  - If one or more abnormal values are found, it creates a task for the associated patient.
**Effects Produced**
  - Adds a new open task titled "Review Abnormal Lab Values ({count} abnormal)", labeled as "abnormal-lab" and "urgent-review" to the patient's workflow.
  - Logs the creation of the task and any errors encountered in the process.
**Error Handling**
  - If anything goes wrong (e.g., the report is missing, or there's a processing error), it logs the error and does not produce any task.
**Summary**
This handler automates the process of flagging abnormal laboratory results for clinical review within Canvas Medical, enhancing the safety net for critical lab findings by ensuring they are not overlooked.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.task import AddTask, TaskStatus
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.v1.data.lab import LabReport
    from logger import log
    class AbnormalLabHandler(BaseHandler):
        """
        A handler that monitors lab reports and creates task notifications
        for abnormal lab values to ensure prompt review.
        Triggers on: LAB_REPORT_CREATED events
        Effects: Creates tasks for abnormal lab values
        """
        RESPONDS_TO = EventType.Name(EventType.LAB_REPORT_CREATED)
        def compute(self) -> list[Effect]:
            """
            This method gets called when a LAB_REPORT_CREATED event is fired.
            It checks for abnormal lab values and creates tasks for them.
            """
            # Get the lab report ID from the event target
            lab_report_id = self.event.target.id
            try:
                # Get the lab report instance with filters applied
                lab_report = LabReport.objects.filter(
                    id=lab_report_id,
                    for_test_only=False,
                    junked=False,
                    patient__isnull=False
                ).first()
                if not lab_report:
                    return []
                patient_id = lab_report.patient.id
                # Check all lab values for abnormal flags
                abnormal_values = []
                for lab_value in lab_report.values.all():
                    # Check if the lab value has an abnormal flag (handle None case)
                    abnormal_flag = getattr(lab_value, 'abnormal_flag', None) or ""
                    if abnormal_flag.strip():
                        abnormal_values.append(lab_value)
                if not abnormal_values:
                    return []
                # Create a task for the abnormal lab values
                abnormal_count = len(abnormal_values)
                task_title = f"Review Abnormal Lab Values ({abnormal_count} abnormal)"
                # Create the task
                task = AddTask(
                    patient_id=patient_id,
                    title=task_title,
                    status=TaskStatus.OPEN,
                    labels=["abnormal-lab", "urgent-review"]
                )
                applied_task = task.apply()
                log.info(f"Created task for {abnormal_count} abnormal lab value(s) in report {lab_report_id}")
                return [applied_task]
            except Exception as e:
                log.error(f"Error processing lab report {lab_report_id}: {str(e)}")
                return []
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/example-abnormal_lab_task_notification/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/example-ai_note_titles/
> **Note:** [View the source](https://github.com/canvas-medical/canvas-plugins/tree/main/example-plugins/ai_note_titles) for this plugin on GitHub. 
##  Description 
Plugin that renames Notes when locked using OpenAI and the contents of the Note.
##  Configuration 
This example plugin defines the following "secrets" in the manifest file:
    ```plaintext
        "variables": [
            {"name": "OPENAI_API_KEY", "sensitive": true}
        ],
    ```
Once defined in the `MANIFEST.json`, set the secrets for your plugin in the Admin UI of your Canvas EMR. [Read more](https://docs.canvasmedical.com/sdk/secrets/)
###  OPENAI_API_KEY 
[OpenAI API Key](https://platform.openai.com/docs/api-reference/authentication)
##  CANVAS_MANIFEST.json 
    ```json
    {
        "sdk_version": "0.1.4",
        "plugin_version": "0.0.1",
        "name": "ai_note_titles",
        "description": "Edit the description in CANVAS_MANIFEST.json",
        "components": {
            "handlers": [
                {
                    "class": "ai_note_titles.handlers.rename_note:Handler",
                    "description": "Renames Notes when locked using OpenAI and the contents of the Note"
                }
            ],
            "commands": [],
            "content": [],
            "effects": [],
            "views": []
        },
        "variables": [
            {"name": "OPENAI_API_KEY", "sensitive": true}
        ],
        "tags": {},
        "references": [],
        "license": "",
        "diagram": false,
        "readme": "./README.md"
    }
    ```
##  handlers/ 
###  rename_note.py 
**Purpose**
This code defines a handler in a Canvas SDK plugin that automatically renames clinical notes when they are locked, using the OpenAI API to generate new titles based on the note content.
**Class Overview**
  - The main class, `Handler`, extends `BaseHandler`.
  - The handler listens for `NOTE_STATE_CHANGE_EVENT_CREATED` events—specifically, when a note's state changes (e.g., gets locked).
**Main Workflow**
  - When a relevant event fires, the `compute` method is triggered.
  - It extracts the note ID from the context.
  - If the note event corresponds to a locking action, it proceeds.
  - It collects the note content and sends it (along with instructions) to OpenAI's API, requesting a concise, descriptive title.
  - If a valid title is returned, it generates an `update` effect to rename the note in Canvas.
**OpenAI Integration**
  - The `get_note_title` function prepares headers and a request payload from the note's structured content and specific instructions.
  - It POSTS this data to OpenAI's API (note: the endpoint used, `/v1/responses`, is likely a placeholder).
  - It parses the response to extract the generated title.
**Supporting Functions**
  - `get_model`: Returns the OpenAI model name (`gpt-4.1`).
  - `get_input`: Serializes note commands (actions or entries inside the note) to JSON for use as the prompt to the language model.
  - `get_instructions`: Supplies explicit instructions and examples to OpenAI, guiding it to create short, clinically meaningful titles.
  - `is_locked_note_event`: Checks if the event is specifically a note being locked (not other state transitions).
**Error Handling**
  - Logs issues if the note ID is missing, the OpenAI call fails, or expected fields are missing in the response.
**Effect on Canvas**
  - If a new title is obtained, issues a `NoteEffect.update()` call to rename the note instance.
**Summary**
This file defines a Canvas plugin handler that listens for notes being locked, then uses OpenAI to generate and set a concise, relevant note title based on its clinical contents.
    ```python
    import json
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.note import Note as NoteEffect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers.base import BaseHandler
    from canvas_sdk.utils.http import Http
    from canvas_sdk.v1.data.command import Command
    from canvas_sdk.v1.data.note import CurrentNoteStateEvent, NoteStates
    from logger import log
    class Handler(BaseHandler):
        """Renames Notes when locked using OpenAI and the contents of the Note."""
        RESPONDS_TO: list[str] = [
            EventType.Name(EventType.NOTE_STATE_CHANGE_EVENT_CREATED),
        ]
        def compute(self) -> list[Effect]:
            """This method gets called when an event of the type RESPONDS_TO is fired."""
            note_id: str | None = self.context.get("note_id")
            if not note_id:
                log.error("No note ID found in context")
                return []
            if not self.is_locked_note_event():
                return []
            new_title = self.get_note_title(note_id)
            if not new_title:
                return []
            return [NoteEffect(instance_id=note_id, title=new_title).update()]
        def get_note_title(self, note_id: str) -> str | None:
            """Get the new note title from the note."""
            headers = {
                "Authorization": f"Bearer {self.secrets.get('OPENAI_API_KEY')}",
                "Content-Type": "application/json",
            }
            payload = {
                "input": self.get_input(note_id),
                "instructions": self.get_instructions(),
                "model": self.get_model(),
                "temperature": 0,
            }
            response = Http().post(
                "https://api.openai.com/v1/responses", headers=headers, data=json.dumps(payload)
            )
            if not response.ok:
                log.error(
                    f"Generate note title request failed: {response.status_code} - {response.text}"
                )
                return None
            response_json = response.json()
            new_title: str | None = None
            try:
                new_title = response_json.get("output")[0].get("content")[0].get("text")
            except Exception as e:
                log.error(f"Failed to get note title from response: {response.text} {e}")
            return new_title
        def get_model(self) -> str:
            """Get the OpenAI model to use."""
            return "gpt-4.1"
        def get_input(self, note_id: str) -> str:
            """Stringified commands within note to be used as input for OpenAI."""
            commands = Command.objects.filter(
                note__id=note_id, entered_in_error__isnull=True, committer__isnull=False
            )
            return json.dumps(list(commands.values("schema_key", "data")))
        def get_instructions(self) -> str:
            """Instructions for OpenAI to use to rename the note."""
            return """
            You are a clinical documentation specialist that generates a clinical note title using 10 words or less.
            This will read by a clinician looking to get a quick overview of the note.
            Return the exact title ONLY and nothing else.
            Examples:
            Ankle edema and amlodipine intolerance, medication change discussion
            Refilled metoprolol succinate ER and rosuvastatin 10 mg tablets
            Follow up call regarding elevated heart rate to 120
            Fall with back pain, unsteady gait, declined ER and HHA
            """
        def is_locked_note_event(self) -> bool:
            """Check if the note is locked."""
            return (
                CurrentNoteStateEvent.objects.values_list("state", flat=True).get(
                    id=self.event.target.id
                )
                == NoteStates.LOCKED
            )
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/example-ai_note_titles/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/example-api_samples/
> **Note:** [View the source](https://github.com/canvas-medical/canvas-plugins/tree/main/example-plugins/api_samples) for this plugin on GitHub. 
Showcases the usage of the SimpleAPI handler
##  Configuration 
Once installed, see the plugin configuration page to set credentials to make authenticated requests.
##  CANVAS_MANIFEST.json 
    ```json
    {
        "sdk_version": "0.1.4",
        "plugin_version": "0.0.1",
        "name": "api_samples",
        "description": "Example usages of the SimpleAPI handler",
        "components": {
            "handlers": [
                {
                    "class": "api_samples.routes.hello_world:HelloWorldAPI",
                    "description": "Returns a json message"
                },
                {
                    "class": "api_samples.routes.email_bounce:EmailBounceAPI",
                    "description": "Creates a task to confirm patient contact info"
                }
            ],
            "commands": [],
            "content": [],
            "effects": [],
            "views": []
        },
        "variables": [
            {"name": "my-api-key", "sensitive": true}
        ],
        "tags": {},
        "references": [],
        "license": "",
        "diagram": false,
        "readme": "./README.md"
    }
    ```
##  routes/ 
###  email_bounce.py 
The code defines an endpoint for handling bounced email events.
**Endpoint**
  - **Path:** `/crm-webhooks/email-bounce`
  - **Method:** POST
  - **Expected Body:** JSON containing `{"mrn": "valid patient MRN"}`
  - **Authorization:** Requires an API key in the `Authorization` header that matches the plugin secret `'my-api-key'`.
**Core Functionality**
  - When a POST request is received: 
    - It authenticates the request by verifying the provided API key against a stored secret.
    - It retrieves the `Patient` object from the database with the given MRN (Medical Record Number) from the request body.
    - It schedules a new open task for that patient: 
      - **Title:** `"Please confirm contact information."`
      - **Due Date:** 5 days from the current UTC date
      - **Label:** `"CRM"`
  - Returns a response indicating both the creation of the task and a confirmation JSON message.
**Intended Use**
This endpoint provides an automated workflow to prompt staff to confirm and update patient contact information in response to an email bounce event, improving contact data quality in clinical operations.
    ```python
    import arrow
    from canvas_sdk.effects.simple_api import JSONResponse, Response
    from canvas_sdk.effects.task import AddTask, TaskStatus
    from canvas_sdk.handlers.simple_api import APIKeyCredentials, SimpleAPIRoute
    from canvas_sdk.v1.data import Patient
    #
    # POST /plugin-io/api/api_samples/crm-webhooks/email-bounce
    # Body: { "mrn": "valid patient MRN" }
    # Headers: "Authorization <your value for 'my-api-key'>"
    #
    class EmailBounceAPI(SimpleAPIRoute):
        PATH = "/crm-webhooks/email-bounce"
        def authenticate(self, credentials: APIKeyCredentials) -> bool:
            return credentials.key == self.secrets["my-api-key"]
        def post(self) -> list[Response]:
            patient = Patient.objects.get(mrn=self.request.json()["mrn"])
            five_days_from_now = arrow.utcnow().shift(days=5).datetime
            task_effect = AddTask(
                patient_id=patient.id,
                title="Please confirm contact information.",
                due=five_days_from_now,
                status=TaskStatus.OPEN,
                labels=["CRM"],
            )
            return [task_effect.apply(), JSONResponse({"message": "Task Created"})]
    ```
###  hello_world.py 
This code defines a simple API endpoint which handles requests to the path `/hello-world`. When a GET request is made to this endpoint, the API responds with a JSON message that says "Hello world!".
**Authentication**
The endpoint requires an API key for authentication. The client must provide an API key (as a header called `Authorization`). The provided key is compared to a value stored in `self.secrets["my-api-key"]`. If the keys match, authentication is successful and the request is allowed; otherwise, it will be denied.
**Response**
A GET request to `/plugin-io/api/api_samples/hello-world` (when authenticated) returns a JSON response in the following format:
    ```json
    {
      "message": "Hello world!"
    }
    ```
    ```python
    from canvas_sdk.effects.simple_api import JSONResponse, Response
    from canvas_sdk.handlers.simple_api import APIKeyCredentials, SimpleAPIRoute
    #
    # GET /plugin-io/api/api_samples/hello-world
    # Headers: "Authorization <your value for 'my-api-key'>"
    #
    class HelloWorldAPI(SimpleAPIRoute):
        PATH = "/hello-world"
        def authenticate(self, credentials: APIKeyCredentials) -> bool:
            return credentials.key == self.secrets["my-api-key"]
        def get(self) -> list[Response]:
            return [JSONResponse({"message": "Hello world!"})]
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/example-api_samples/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/example-aws_s3/
> **Note:** [View the source](https://github.com/canvas-medical/canvas-plugins/tree/main/example-plugins/aws_s3) for this plugin on GitHub. 
##  Description 
Plugin that provides a SimpleAPI for managing AWS S3 objects, including listing, uploading, downloading, deleting files, and generating presigned URLs. It also includes a chart application that renders a form interface for interacting with S3 directly from the chart.
##  Configuration 
This example plugin defines the following "secrets" in the manifest file:
    ```plaintext
        "variables": [
            {"name": "S3Key", "sensitive": true},
            {"name": "S3Secret", "sensitive": true},
            {"name": "S3Region", "sensitive": false},
            {"name": "S3Bucket", "sensitive": false}
        ],
    ```
Once defined in the `MANIFEST.json`, set the secrets for your plugin in the Admin UI of your Canvas EMR. [Read more](https://docs.canvasmedical.com/sdk/secrets/)
###  S3Key 
Your AWS Access Key ID.
###  S3Secret 
Your AWS Secret Access Key.
###  S3Region 
The AWS region where your S3 bucket is located (e.g., `us-east-1`).
###  S3Bucket 
The name of your S3 bucket.
##  CANVAS_MANIFEST.json 
    ```json
    {
      "sdk_version": "0.81.0",
      "plugin_version": "0.0.1",
      "name": "aws_manip",
      "description": "use AWS S3 to store, retrieve and delete documents",
      "components": {
        "handlers": [
          {
            "class": "aws_manip.handlers.aws_manip:AwsManip",
            "description": "AWS extractor based on AWS S3"
          }
        ],
        "applications": [
          {
            "class": "aws_manip.handlers.aws_form_app:AwsFormApp",
            "name": "AWS S3 Document Management",
            "description": "AWS S3 manip",
            "icon": "static/aws_manip.png",
            "scope": "patient_specific",
            "show_in_panel": false
          }
        ],
        "commands": [],
        "content": [],
        "effects": [],
        "views": []
      },
      "variables": [
        {"name": "S3Key", "sensitive": true},
        {"name": "S3Secret", "sensitive": true},
        {"name": "S3Region", "sensitive": false},
        {"name": "S3Bucket", "sensitive": false}
      ],
      "tags": {},
      "references": [],
      "license": "",
      "diagram": false,
      "readme": "./README.md"
    }
    ```
##  handlers/ 
###  aws_manip.py 
**Purpose**
This code defines a SimpleAPI handler that exposes REST endpoints for managing objects in an Amazon S3 bucket using the Canvas SDK's AWS S3 client.
**Class Overview**
  - The main class, `AwsManip`, extends `SimpleAPI`.
  - It creates an S3 client using credentials stored in plugin secrets.
**Main Workflow**
  - `GET /list_items` — Lists all objects in the configured S3 bucket.
  - `GET /get_item/<item_key>` — Retrieves an object's content by its key.
  - `GET /presigned_url/<item_key>` — Generates a presigned URL for temporary (1-hour) access to an object.
  - `POST /upload_item/<item_key>` — Uploads content to S3, handling both text and binary content types.
  - `DELETE /delete_item/<item_key>` — Deletes an object from S3 by key.
**S3 Client Integration**
  - The `_s3_client` method creates an `S3` client instance from `canvas_sdk.clients.aws.libraries`, configured with `S3Credentials` from plugin secrets.
  - Each endpoint checks `client.is_ready()` before performing operations.
    ```python
    from http import HTTPStatus
    from aws_manip.constants.secrets import Secrets
    from canvas_sdk.clients.aws.libraries import S3
    from canvas_sdk.clients.aws.structures import Credentials as S3Credentials
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.simple_api import JSONResponse, PlainTextResponse, Response
    from canvas_sdk.handlers.simple_api import Credentials, SimpleAPI, api
    class AwsManip(SimpleAPI):
        """Simple API handler for AWS S3 object management operations."""
        PREFIX = None
        USER_TYPE_STAFF = "Staff"
        def authenticate(self, credentials: Credentials) -> bool:
            """Authenticate API requests.
            Args:
                credentials: The credentials provided with the request.
            Returns:
                True to allow all requests (authentication bypassed).
            """
            return True
        def _s3_client(self) -> S3:
            """Create and configure an S3 client with credentials from secrets.
            Returns:
                Configured S3 client instance.
            """
            return S3(
                S3Credentials(
                    key=self.secrets[Secrets.s3_key],
                    secret=self.secrets[Secrets.s3_secret],
                    region=self.secrets[Secrets.s3_region],
                    bucket=self.secrets[Secrets.s3_bucket],
                )
            )
        @api.get("/list_items")
        def list_items(self) -> list[Response | Effect]:
            """List all objects in the S3 bucket."""
            client = self._s3_client()
            if client.is_ready():
                content = [p.key for p in client.list_s3_objects("")]
                status_code = HTTPStatus(HTTPStatus.OK)
                return [JSONResponse(content, status_code=status_code)]
            return []
        @api.get("/get_item/<item_key>")
        def get_item(self) -> list[Response | Effect]:
            """Retrieve an object's content from S3 by key."""
            item_key = self.request.path_params["item_key"]
            client = self._s3_client()
            if client.is_ready() and item_key:
                content = client.access_s3_object(item_key).content
                status_code = HTTPStatus(HTTPStatus.OK)
                return [Response(content, status_code=status_code)]
            return []
        @api.get("/presigned_url/<item_key>")
        def presigned_url(self) -> list[Response | Effect]:
            """Generate a presigned URL for temporary access to an S3 object."""
            item_key = self.request.path_params["item_key"]
            client = self._s3_client()
            if client.is_ready() and item_key:
                content = client.generate_presigned_url(item_key, 3600)
                status_code = HTTPStatus(HTTPStatus.OK)
                return [PlainTextResponse(content, status_code=status_code)]
            return []
        @api.post("/upload_item/<item_key>")
        def upload_item(self) -> list[Response | Effect]:
            """Upload content to S3 with the specified key."""
            item_key = self.request.path_params["item_key"]
            client = self._s3_client()
            content = self.request.body
            content_type = self.request.content_type
            if client.is_ready() and item_key:
                if content_type == "text/plain":
                    response = client.upload_text_to_s3(item_key, content.decode("utf-8"))
                else:
                    response = client.upload_binary_to_s3(item_key, content, content_type)
                return [Response(response.content, status_code=response.status_code)]
            return []
        @api.delete("/delete_item/<item_key>")
        def delete_item(self) -> list[Response | Effect]:
            """Delete an object from S3 by key."""
            item_key = self.request.path_params["item_key"]
            client = self._s3_client()
            if client.is_ready() and item_key:
                content = client.delete_object(item_key).content
                status_code = HTTPStatus(HTTPStatus.OK)
                return [Response(content, status_code=status_code)]
            return []
    ```
###  aws_form_app.py 
**Purpose**
This code defines an Application handler that launches a modal form in the right chart pane for interacting with the S3 management API endpoints.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.launch_modal import LaunchModalEffect
    from canvas_sdk.handlers.application import Application
    from canvas_sdk.templates import render_to_string
    class AwsFormApp(Application):
        """Application handler for launching the AWS S3 management form interface."""
        PLUGIN_API_BASE_ROUTE = "/plugin-io/api/aws_manip"
        def on_open(self) -> Effect:
            """Render and launch the AWS S3 management modal form."""
            content = render_to_string(
                "templates/aws_form.html",
                {
                    "listItemsURL": f"{self.PLUGIN_API_BASE_ROUTE}/list_items",
                    "getItemURL": f"{self.PLUGIN_API_BASE_ROUTE}/get_item",
                    "presignedUrlURL": f"{self.PLUGIN_API_BASE_ROUTE}/presigned_url",
                    "uploadItemURL": f"{self.PLUGIN_API_BASE_ROUTE}/upload_item",
                    "deleteItemURL": f"{self.PLUGIN_API_BASE_ROUTE}/delete_item",
                },
            )
            return LaunchModalEffect(
                content=content,
                target=LaunchModalEffect.TargetType.RIGHT_CHART_PANE,
            ).apply()
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/example-aws_s3/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/example-charting_api_examples/
> **Note:** [View the source](https://github.com/canvas-medical/canvas-plugins/tree/main/example-plugins/charting_api_examples) for this plugin on GitHub. 
This example plugin provides several examples of APIs you might define when automating charting in Canvas.
  - Notes 
    - Creating a note
    - Getting information about a note
    - Searching for notes by patient, type, and date of service
    - Adding billing line items to a note
  - Commands 
    - Creating a command
    - Creating multiple commands from a single request
    - Creating a command and committing it in a single request
##  Configuration 
All of the example endpoints in this plugin are protected with [API key authentication](https://docs.canvasmedical.com/sdk/handlers-simple-api-http/#api-key-1). Once installed, you'll need to set the `simpleapi-api-key` value on the plugin's configuration page in your EHR.
##  Endpoint Documentation 
###  Search Notes 
`GET /plugin-io/api/charting_api_examples/notes/`
This endpoint allows the retrieval of an optionally filtered set of notes. The results are paginated, and the client can exert some control over the page size. The response body will include an attribute, `next_page`, which will either contain a URL to the next page of the same filtered set or be `null`, indicating there are no more records to fetch.
####  Optional query parameters: 
#####  limit 
_int_
Determines the number of results to return per page.
  - If unspecified, the default is 10.
  - If a number less than 1 is specified, 1 will be used.
  - If a number greate than 100 is specified, 100 will be used.
#####  offset 
_int_
Number of records to skip with returning results. When used with **limit** , this enables the pagination of results.
  - If unspecified, the default is 0.
  - If a number less than 0 is specified, 0 will be used.
#####  patient_id 
_str_
Filters the notes returned to just those associated with the given patient.
#####  note_type 
_coding_
You can search by just the code value or you can search by the system and code in the format "system|code" (e.g. `http://snomed.info/sct|308335008`).
#####  datetime_of_service 
_iso8601 formatted datetime string_
Exact match for notes with the given datetime.
#####  datetime_of_service__gt 
_iso8601 formatted datetime string_
Filter to notes occurring after the given datetime.
#####  datetime_of_service__gte 
_iso8601 formatted datetime string_
Filter to notes occurring at or after the given datetime.
#####  datetime_of_service__lt 
_iso8601 formatted datetime string_
Filter to notes occurring before the given datetime.
#####  datetime_of_service__lte 
_iso8601 formatted datetime string_
Filter to notes occurring at or before the given datetime.
####  Example Request 
    ```bash
    curl --request GET \
      --url 'https://training.canvasmedical.com/plugin-io/api/charting_api_examples/notes/?limit=2&offset=4' \
      --header 'Authorization: <your-api-key-goes-here>'
    ```
####  Example Response 
    ```json
    {
      "next_page": "https://training.canvasmedical.com/plugin-io/api/charting_api_examples/notes/?limit=2&offset=6",
      "count": 2,
      "notes": [
        {
          "id": "10ff2047-6301-4ab4-81cd-b500e7df8ef7",
          "patient_id": "5350cd20de8a470aa570a852859ac87e",
          "provider_id": "5843991a8c934118ab4f424c839b340f",
          "datetime_of_service": "2025-02-21 23:31:45.627894+00:00",
          "note_type": {
            "id": "c5df4f03-58e4-442b-ad6c-0d3dadc6b726",
            "name": "Office visit",
            "coding": {
              "display": "Office Visit",
              "code": "308335008",
              "system": "http://snomed.info/sct"
            }
          }
        },
        {
          "id": "4dba128f-96cc-4dd0-814b-a064bfdcde7e",
          "patient_id": "5350cd20de8a470aa570a852859ac87e",
          "provider_id": "336159560091471cb6b0e149d9054697",
          "datetime_of_service": "2025-02-21 23:31:45.928071+00:00",
          "note_type": {
            "id": "c5df4f03-58e4-442b-ad6c-0d3dadc6b726",
            "name": "Office visit",
            "coding": {
              "display": "Office Visit",
              "code": "308335008",
              "system": "http://snomed.info/sct"
            }
          }
        }
      ]
    }
    ```
###  Read a Note 
`GET /plugin-io/api/charting_api_examples/notes/<note-id>/`
####  Example Response 
    ```json
    {
      "note": {
        "id": "1490b8db-00a9-47d9-9170-ec142460b586",
        "patient_id": "5350cd20de8a470aa570a852859ac87e",
        "provider_id": "6b33e69474234f299a56d480b03476d3",
        "datetime_of_service": "2025-10-02 23:30:00+00:00",
        "state": "NEW",
        "note_type": {
          "id": "c5df4f03-58e4-442b-ad6c-0d3dadc6b726",
          "name": "Office visit",
          "coding": {
            "display": "Office Visit",
            "code": "308335008",
            "system": "http://snomed.info/sct"
          }
        }
      }
    }
    ```
###  Create a Note 
`POST /plugin-io/api/charting_api_examples/notes/`
####  Example Request Body 
    ```json
    {
      "practice_location_id": "306b19f0-231a-4cd4-ad2d-a55c885fd9f8",
      "note_type_id": "c5df4f03-58e4-442b-ad6c-0d3dadc6b726",
      "patient_id": "5350cd20de8a470aa570a852859ac87e",
      "provider_id": "6b33e69474234f299a56d480b03476d3",
      "datetime_of_service": "2025-10-04 23:30:00",
      "title": "My cool note"
    }
    ```
###  Add Billing Line Item to a Note 
`POST /plugin-io/api/charting_api_examples/notes/<note-id>/billing_line_items/`
####  Example Request Body 
    ```json
    {
      "cpt_code": "98008"
    }
    ```
###  Add a Diagnose Command to a Note 
`POST /plugin-io/api/charting_api_examples/notes/<note-id>/diagnose/`
`icd10_code` is required, but `committed` may be true, false, or omitted entirely.
####  Example Request Body 
    ```json
    {
      "icd10_code": "E119",
      "committed": true
    }
    ```
###  Add Multiple Commands to a Note 
`POST /plugin-io/api/charting_api_examples/notes/<note-id>/prechart/`
####  Example Request Body 
    ```json
    null
    ```
##  CANVAS_MANIFEST.json 
    ```json
    {
        "sdk_version": "0.1.4",
        "plugin_version": "0.0.1",
        "name": "charting_api_examples",
        "description": "A series of custom API routes that showcase SDK charting functionality",
        "components": {
            "handlers": [
                {
                    "class": "charting_api_examples.routes.notes:NoteAPI",
                    "description": "Endpoints that showcase interactions with notes."
                },
                {
                    "class": "charting_api_examples.routes.billing_line_items:BillingLineItemAPI",
                    "description": "Endpoints for interacting with billing line items on a note."
                },
                {
                    "class": "charting_api_examples.routes.commands:CommandAPI",
                    "description": "Endpoints for interacting with commands on a note."
                }
            ],
            "commands": [],
            "content": [],
            "effects": [],
            "views": []
        },
        "variables": [
            {"name": "simpleapi-api-key", "sensitive": true}
        ],
        "tags": {},
        "references": [],
        "license": "",
        "diagram": false,
        "readme": "./README.md"
    }
    ```
##  routes/ 
###  commands.py 
This file provides an example of how to define REST API endpoints in a Canvas-based plugin for inserting medical charting commands to clinical notes, with validation, error handling, and support for both single and batch command operations.
**Endpoints**
  1. `/notes/<id>/diagnose/` (POST): 
     - Adds a DiagnoseCommand to the specified note.
     - Expects a JSON body with at least an "icd10_code" parameter, and optionally "committed" (boolean).
     - If "committed" is true, the diagnose command is also committed immediately after being originated.
     - Handles missing attribute errors and note-not-found situations gracefully, responding with appropriate error messages and status codes.
  2. `/notes/<id>/prechart/` (POST): 
     - Initiates (originates) several commands at once for a note: ReasonForVisitCommand, PhysicalExamCommand, DiagnoseCommand, and PlanCommand.
     - Designed to quickly set up the structure for clinical pre-charting with a single request.
**Implementation Highlights**
  - The API is protected with API key authentication via `APIKeyAuthMixin`.
  - Uses utility functions (`get_note_from_path_params`, `note_not_found_response`) to locate notes and standardize error responses.
  - Returns both command effects (operations that are intended to be executed in the Canvas system) and standard JSON responses.
  - Uses status codes according to best practices, addressing Python version differences by using explicit numeric codes where needed.
**Canvas SDK Features Used**
  - Commands: `DiagnoseCommand`, `PhysicalExamCommand`, `PlanCommand`, `ReasonForVisitCommand` — each representing an action to be performed on a note.
  - Effects: Chainable "originate" (create/begin the command) and "commit" (finalize/commit the command).
    ```python
    from http import HTTPStatus
    from uuid import uuid4
    from canvas_sdk.commands import (
        DiagnoseCommand,
        PhysicalExamCommand,
        PlanCommand,
        ReasonForVisitCommand,
    )
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.simple_api import JSONResponse, Response
    from canvas_sdk.handlers.simple_api import APIKeyAuthMixin, SimpleAPI, api
    from canvas_sdk.v1.data.note import Note
    from charting_api_examples.util import get_note_from_path_params, note_not_found_response
    class CommandAPI(APIKeyAuthMixin, SimpleAPI):
        PREFIX = "/notes"
        """
        This shows how you can create an endpoint to insert a particular type of
        command. In this example, it's a diagnose command. It can be committed or
        left uncommitted.
        POST /plugin-io/api/charting_api_examples/notes/<note-id>/diagnose/
        Headers: "Authorization <your value for 'simpleapi-api-key'>"
        Body: {
            "icd10_code": "E11.9",
            "committed": false
        }
        """
        @api.post("/<id>/diagnose/")
        def add_diagnose_command(self) -> list[Response | Effect]:
            required_attributes = {"icd10_code",}
            request_body = self.request.json()
            missing_attributes = required_attributes - request_body.keys()
            if len(missing_attributes) > 0:
                return [
                    JSONResponse(
                        {"error": f"Missing required attribute(s): {', '.join(missing_attributes)}"},
                        # Normally you should use a constant, but this status
                        # code's constant changes in 3.13 from
                        # UNPROCESSABLE_ENTITY to UNPROCESSABLE_CONTENT. Using the
                        # number directly here avoids that future breakage.
                        status_code=422,
                    )
                ]
            note = get_note_from_path_params(self.request.path_params)
            if not note:
                return note_not_found_response()
            diagnose_command = DiagnoseCommand(
                note_uuid=str(note.id),
                icd10_code=request_body["icd10_code"].upper(),
            )
            if request_body.get("committed"):
                # To chain command effects, you must know what the command's id
                # is. To accomplish that, we set the id ourselves rather than
                # allow the database to assign one.
                diagnose_command.command_uuid = str(uuid4())
                command_effects = [diagnose_command.originate(), diagnose_command.commit()]
            else:
                command_effects = [diagnose_command.originate()]
            return [
                *command_effects,
                JSONResponse({"message": "Command data accepted for creation"}, status_code=HTTPStatus.ACCEPTED)
            ]
        """
        This shows how you can originate many commands from the same request.
        POST /plugin-io/api/charting_api_examples/notes/<note-id>/prechart/
        Headers: "Authorization <your value for 'simpleapi-api-key'>"
        Body: {
        }
        """
        @api.post("/<id>/prechart/")
        def add_precharting_commands(self) -> list[Response | Effect]:
            request_body = self.request.json()
            note = get_note_from_path_params(self.request.path_params)
            if not note:
                return note_not_found_response()
            rfv = ReasonForVisitCommand(note_uuid=str(note.id))
            exam = PhysicalExamCommand(note_uuid=str(note.id))
            diagnose = DiagnoseCommand(note_uuid=str(note.id))
            plan = PlanCommand(note_uuid=str(note.id))
            return [
                rfv.originate(),
                exam.originate(),
                diagnose.originate(),
                plan.originate(),
                JSONResponse({"message": "Command data accepted for creation"}, status_code=HTTPStatus.ACCEPTED)
            ]
    ```
###  notes.py 
Defines an API endpoint for working with clinical notes. The API supports listing, creating, and retrieving notes, with filtering and pagination features. Authentication is handled through an API key.
**Endpoints**
  - **GET /notes/**
    - Returns a paginated list of notes.
    - Supports filters: 
      - `patient_id`: Filter notes for a specific patient.
      - `note_type`: Filter by note type (by code or by system | code).  
---|---  
      - `datetime_of_service`, `datetime_of_service__gt`, `datetime_of_service__gte`, `datetime_of_service__lt`, `datetime_of_service__lte`: Date/time based filters.
    - Pagination: 
      - `limit`: Number of results (default 10, min 1, max 100).
      - `offset`: Pagination offset (default 0, min 0).
    - If more results exist after the current page, a `next_page` link is included.
    - Returns a JSON object with `notes` (list), `count` (number of notes returned), and `next_page` (URL or None).
  - **POST /notes/**
    - Creates a new note.
    - Requires a JSON body with: 
      - `note_type_id`, `datetime_of_service` (as string), `patient_id`, `practice_location_id`, `provider_id`, `title`
    - If required fields are missing, returns an error with status 422.
    - Otherwise, initiates note creation and immediately returns an accepted response (`202 Accepted`) with a confirmation message.
  - **GET /notes/ <note-id>/**
    - Returns details for a specific note by its ID.
    - If the note doesn't exist, returns a "not found" response.
    - If found, returns note details, including its state (from `CurrentNoteStateEvent`), patient/provider IDs, datetime, and type info (id, name, coding).
**Helpers and Utilities**
  - Uses utilities such as `get_note_from_path_params` and `note_not_found_response` for ID lookup and error handling.
  - Uses Django-style ORM filters and queryset slicing.
  - Uses the `arrow` library for date parsing.
  - All endpoints expect authentication via the `simpleapi-api-key`.
    ```python
    import arrow
    from http import HTTPStatus
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.note.note import Note as NoteEffect
    from canvas_sdk.effects.simple_api import JSONResponse, Response
    from canvas_sdk.handlers.simple_api import APIKeyAuthMixin, SimpleAPI, api
    from canvas_sdk.v1.data.note import Note, CurrentNoteStateEvent
    from charting_api_examples.util import get_note_from_path_params, note_not_found_response
    class NoteAPI(APIKeyAuthMixin, SimpleAPI):
        PREFIX = "/notes"
        """
        GET /plugin-io/api/charting_api_examples/notes/
        Headers: "Authorization <your value for 'simpleapi-api-key'>"
        """
        @api.get("/")
        def index(self) -> list[Response | Effect]:
            notes = Note.objects.select_related('patient', 'provider', 'note_type_version').order_by("dbid")
            query_params = self.request.query_params
            # User specified, default 10, min 1, max 100
            limit = min(max(int(query_params.get("limit", 10)), 1), 100)
            # User specified, default 0, min 0, no max
            offset = max(int(query_params.get("offset", 0)), 0)
            if "patient_id" in query_params:
                notes = notes.filter(patient__id=query_params["patient_id"])
            if "note_type" in query_params:
                # You can search by just the code value or you can search by the
                # system and code in the format system|code (e.g
                # http://snomed.info/sct|308335008).
                if "|" in query_params["note_type"]:
                    system, code = query_params["note_type"].split("|")
                    notes = notes.filter(note_type_version__system=system, note_type_version__code=code)
                else:
                    notes = notes.filter(note_type_version__code=query_params["note_type"])
            if "datetime_of_service" in query_params:
                notes = notes.filter(datetime_of_service=query_params["datetime_of_service"])
            if "datetime_of_service__gt" in query_params:
                notes = notes.filter(datetime_of_service__gt=query_params["datetime_of_service__gt"])
            if "datetime_of_service__gte" in query_params:
                notes = notes.filter(datetime_of_service__gte=query_params["datetime_of_service__gte"])
            if "datetime_of_service__lt" in query_params:
                notes = notes.filter(datetime_of_service__lt=query_params["datetime_of_service__lt"])
            if "datetime_of_service__lte" in query_params:
                notes = notes.filter(datetime_of_service__lte=query_params["datetime_of_service__lte"])
            # If there are more results matching the filter after the ones we're
            # returning, provide the link to the next page with the proper offset.
            # If there aren't any more results, return None so the client can tell
            # that there are no more results to fetch.
            link_to_next = None
            if notes[offset+limit:].count() > 0:
                requested_uri = self.context.get("absolute_uri")
                if "offset" in query_params:
                    link_to_next = requested_uri.replace(f"offset={offset}", f"offset={offset+limit}")
                else:
                    param_separator = "?" if len(query_params) == 0 else "&"
                    link_to_next = requested_uri + f"{param_separator}offset={offset+limit}"
            # Apply limit and offset
            notes = notes[offset:offset+limit]
            count = len(notes)
            return [
                JSONResponse({
                    "next_page": link_to_next,
                    "count": count,
                    "notes": [{
                        "id": str(note.id),
                        "patient_id": str(note.patient.id),
                        "provider_id": str(note.provider.id),
                        "datetime_of_service": str(note.datetime_of_service),
                        "note_type": {
                            "id": str(note.note_type_version.id),
                            "name": note.note_type_version.name,
                            "coding": {
                                "display": note.note_type_version.display,
                                "code": note.note_type_version.code,
                                "system": note.note_type_version.system,
                            },
                        },
                    } for note in notes]
                })
            ]
        """
        POST /plugin-io/api/charting_api_examples/notes/
        Headers: "Authorization <your value for 'simpleapi-api-key'>"
        Body: {
    		"note_type_id": "c5df4f03-58e4-442b-ad6c-0d3dadc6b726",
            "datetime_of_service": "2025-02-21 23:31:42",
    		"patient_id": "5350cd20de8a470aa570a852859ac87e",
    		"practice_location_id": "306b19f0-231a-4cd4-ad2d-a55c885fd9f8",
    		"provider_id": "6b33e69474234f299a56d480b03476d3",
    		"title": "My Note Title",
        }
        """
        @api.post("/")
        def create(self) -> list[Response | Effect]:
            required_attributes = {
                "note_type_id",
                "datetime_of_service",
                "patient_id",
                "practice_location_id",
                "provider_id",
                "title",
            }
            request_body = self.request.json()
            missing_attributes = required_attributes - request_body.keys()
            if len(missing_attributes) > 0:
                return [
                    JSONResponse(
                        {"error": f"Missing required attribute(s): {', '.join(missing_attributes)}"},
                        # Normally you should use a constant, but this status
                        # code's constant changes in 3.13 from
                        # UNPROCESSABLE_ENTITY to UNPROCESSABLE_CONTENT. Using the
                        # number directly here avoids that future breakage.
                        status_code=422,
                    )
                ]
            note_type_id = request_body["note_type_id"]
            datetime_of_service = arrow.get(request_body["datetime_of_service"]).datetime
            patient_id = request_body["patient_id"]
            practice_location_id = request_body["practice_location_id"]
            provider_id = request_body["provider_id"]
            title = request_body["title"]
            note_effect = NoteEffect(
                note_type_id=note_type_id,
                datetime_of_service=datetime_of_service,
                patient_id=patient_id,
                practice_location_id=practice_location_id,
                provider_id=provider_id,
                title=title,
            )
            return [
                note_effect.create(),
                JSONResponse({"message": "Note data accepted for creation"}, status_code=HTTPStatus.ACCEPTED)
            ]
        """
        GET /plugin-io/api/charting_api_examples/notes/<note-id>/
        Headers: "Authorization <your value for 'simpleapi-api-key'>"
        """
        @api.get("/<id>/")
        def read(self) -> list[Response | Effect]:
            note = get_note_from_path_params(self.request.path_params)
            if not note:
                return note_not_found_response()
            status_code = HTTPStatus.OK
            current_note_state = CurrentNoteStateEvent.objects.get(note=note).state
            response = {
                "note": {
                    "id": str(note.id),
                    "patient_id": str(note.patient.id),
                    "provider_id": str(note.provider.id),
                    "datetime_of_service": str(note.datetime_of_service),
                    "state": current_note_state,
                    "note_type": {
                        "id": str(note.note_type_version.id),
                        "name": note.note_type_version.name,
                        "coding": {
                            "display": note.note_type_version.display,
                            "code": note.note_type_version.code,
                            "system": note.note_type_version.system,
                        },
                    },
                },
            }
            return [JSONResponse(response, status_code=status_code)]
    ```
###  billing_line_items.py 
This file defines an API endpoint for adding billing line items (represented by CPT codes) to a note.
**Key Components**
  - Imports utility and SDK classes for effects, API handling, and response generation.
  - Defines a class, `BillingLineItemAPI`, which inherits API authentication and handling functionalities.
  - Registers an HTTP POST endpoint at `/notes/<note-id>/billing_line_items/`.
  - Expects an API key in the request header for authentication.
  - Expects the request body to be JSON with a "cpt_code" attribute.
    ```python
    from http import HTTPStatus
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.billing_line_item import AddBillingLineItem
    from canvas_sdk.effects.simple_api import JSONResponse, Response
    from canvas_sdk.handlers.simple_api import APIKeyAuthMixin, SimpleAPI, api
    from canvas_sdk.v1.data.note import Note
    from charting_api_examples.util import get_note_from_path_params, note_not_found_response
    class BillingLineItemAPI(APIKeyAuthMixin, SimpleAPI):
        PREFIX = "/notes"
        """
        POST /plugin-io/api/charting_api_examples/notes/<note-id>/billing_line_items/
        Headers: "Authorization <your value for 'simpleapi-api-key'>"
        Body: {
            "cpt_code": "98006"
        }
        """
        @api.post("/<id>/billing_line_items/")
        def add_billing_line_item(self) -> list[Response | Effect]:
            required_attributes = {"cpt_code",}
            request_body = self.request.json()
            missing_attributes = required_attributes - request_body.keys()
            if len(missing_attributes) > 0:
                return [
                    JSONResponse(
                        {"error": f"Missing required attribute(s): {', '.join(missing_attributes)}"},
                        # Normally you should use a constant, but this status
                        # code's constant changes in 3.13 from
                        # UNPROCESSABLE_ENTITY to UNPROCESSABLE_CONTENT. Using the
                        # number directly here avoids that future breakage.
                        status_code=422,
                    )
                ]
            note = get_note_from_path_params(self.request.path_params)
            if not note:
                return note_not_found_response()
            # To see what else you can do with billing line items, visit our docs:
            # https://docs.canvasmedical.com/sdk/effect-billing-line-items/#adding-a-billing-line-item
            effect = AddBillingLineItem(
                note_id=str(note.id),
                cpt=request_body["cpt_code"],
            )
            return [
                effect.apply(),
                JSONResponse({"message": "Billing line item data accepted for creation"}, status_code=HTTPStatus.ACCEPTED)
            ]
    ```
##  util.py 
This file provides utility functions for working with Note objects. The utilities include input validation, standard JSON error responses, and fetching Note objects by ID.
**Function: is_valid_uuid**
This function checks whether a given string is a valid UUID (specifically version 4).
  - Takes a single string argument.
  - Returns True if the string is a properly formatted version 4 UUID, False otherwise.
**Function: note_not_found_response**
This function returns a standardized JSON error response indicating that a requested Note was not found.
  - Uses JSONResponse from the SDK.
  - Sets the response status to HTTP 404 (NOT FOUND) and the body to {"error": "Note not found."}.
**Function: get_note_from_path_params**
This function attempts to retrieve a Note object using a dictionary of path parameters.
  - Extracts the "id" from path_params.
  - Validates the ID as a UUID using is_valid_uuid.
  - If invalid, returns None.
  - If valid, attempts to retrieve a Note object with the given ID (using Note.objects.get).
  - If the Note does not exist, catches the DoesNotExist exception and returns None.
  - Returns the Note object if found, or None if not found/invalid.
**Imports and External Dependencies**
  - uuid.UUID: For validating UUIDs.
  - http.HTTPStatus: For standardized HTTP status codes.
  - canvas_sdk.effects.simple_api.JSONResponse: For returning consistent API responses.
  - canvas_sdk.v1.data.note.Note: For interacting with Note objects from the Canvas SDK.
    ```python
    from uuid import UUID
    from http import HTTPStatus
    from canvas_sdk.effects.simple_api import JSONResponse
    from canvas_sdk.v1.data.note import Note
    def is_valid_uuid(possible_uuid):
        try:
            uuid_obj = UUID(possible_uuid, version=4)
        except ValueError:
            return False
        return str(uuid_obj) == possible_uuid
    def note_not_found_response():
        return JSONResponse(
            {"error": "Note not found."},
            status_code=HTTPStatus.NOT_FOUND,
        )
    def get_note_from_path_params(path_params) -> Note | None:
        note_id = path_params["id"]
        # Ensure the note id is a valid UUID
        if not is_valid_uuid(note_id):
            return None
        try:
            note = Note.objects.get(id=note_id)
        except (Note.DoesNotExist):
            return None
        return note
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/example-charting_api_examples/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/example-example_chart_app/
> **Note:** [View the source](https://github.com/canvas-medical/canvas-plugins/tree/main/example-plugins/example_chart_app) for this plugin on GitHub. 
An example custom UI Application for the patient chart. This particular application loads the active staff user and displays some attributes about them (ex: their name), and it also loads templated HTML and JavaScript to make a REST call to a user-defined endpoint, right in the Canvas EMR.
##  SDK Features 
  - Creates a `GET` [Simple API](/sdk/handlers-simple-api-http/) endpoint that retrieves the logged in [Staff user](/sdk/data-staff/) from the event context, [renders a template](/sdk/layout-effect/#custom-html-and-django-templates), and returns HTML content
  - Creates a `POST` [Simple API](/sdk/handlers-simple-api-http/) endpoint that [creates a task](/sdk/effect-tasks/#adding-a-task)
  - Defines a custom template containing JavaScript that makes a REST call to the user-defined `POST` endpoint above
  - Adds an [Application](/sdk/handlers-applications/) effect that, on open, returns a [LaunchModalEffect](/sdk/layout-effect/#modals) in the right chart pane with content from the `GET` endpoint
##  Configuration 
These SimpleAPI endpoints use the [StaffSessionAuthMixin](/sdk/handlers-simple-api-http/#staff-session)
The `CANVAS_MANIFEST.json` file defines attributes specific to the Application, including scope (`patient_specific`) and icon image.
##  Structure 
    ```plaintext
    example_chart_app/
    ├── applications/
    │   ├── __init__.py
    │   ├── my_application.py     # Defines API endpoints and Application
    ├── assets
    |   ├──rx.png                 # Image file
    ├── templates/
    │   └── custom_ui.html        # HTML template for visualization UI
    ├── CANVAS_MANIFEST.json      # Plugin configuration
    └── README.md                 # Documentation
    ```
##  CANVAS_MANIFEST.json 
    ```json
    {
        "sdk_version": "0.1.4",
        "plugin_version": "0.0.1",
        "name": "example_chart_app",
        "description": "Used to test various canvas plugins functionality",
        "url_permissions": [],
        "components": {
            "handlers": [
                {
                    "class": "example_chart_app.applications.my_application:MyApi",
                    "description": "Provides api for the application",
                    "data_access": {
                        "event": "",
                        "read": [],
                        "write": []
                    }
                }
            ],
            "applications": [
                {
                    "class": "example_chart_app.applications.my_application:MyChartApplication",
                    "name": "Example Chart App",
                    "description": "Show a custom, interactive UI in the chart",
                    "scope": "patient_specific",
                    "icon": "assets/rx.png"
                }
            ],
            "commands": [],
            "content": [],
            "effects": [],
            "views": []
        },
        "variables": [],
        "tags": {},
        "references": [],
        "license": "",
        "diagram": false,
        "readme": "./README.md"
    }
    ```
##  templates/ 
###  custom-ui.html 
This is the html template called by the [`render_to_string` function](/sdk/layout-effect/#custom-html-and-django-templates) with a `logged_in_staff` object. It contains styling and custom Javascript to call the user-defined POST endpoint when a specific element is clicked.
##  applications/ 
###  my_application.py 
This file defines an application plugin for Canvas Medical using the Canvas SDK. It includes a custom application (`MyChartApplication`) and an API (`MyApi`) for rendering a modal user interface pane and handling related actions, such as adding tasks.
**MyChartApplication Class**
  - Subclasses the `Application` class from the Canvas SDK.
  - The `on_open` method is triggered when the application is opened. It retrieves the current patient's ID from `self.context`.
  - It returns a `LaunchModalEffect`, which opens a custom modal UI in the right chart pane. The URL for this modal includes the patient ID as a query parameter.
**MyApi Class**
  - Subclasses both `StaffSessionAuthMixin` and `SimpleAPI`, enabling API definition with authentication.
  - Defines two endpoints using the `@api` decorator: a GET and a POST.
**GET /custom-ui**
  - Endpoint: `/custom-ui`
  - Retrieves the logged-in staff user via the header `"canvas-logged-in-user-id"` and the Canvas SDK Staff data model (but it could retrieve any data!)
  - Renders a template (`templates/custom-ui.html`) with context including the logged-in staff and the selected patient ID.
  - Returns the rendered HTML as an `HTMLResponse` with HTTP 200 (OK) status.
**POST /add-task**
  - Endpoint: `/add-task`
  - Creates a new task for the specified patient (using `patient_id` from the posted JSON payload).
  - Sets the task to be due in 5 days from the current UTC time and sets its status to open.
  - Returns two responses: 
    - The effect to add a task (which also triggers any related UI updates).
    - A JSON response with a success message and HTTP 202 (Accepted) status.
**Miscellaneous**
  - Imports various components and utilities from the Canvas SDK, such as effects for UI actions, response types, authentication mixins, and template rendering.
  - Uses the `arrow` library for time/date handling.
**Summary**
The code provides a Canvas plugin that opens a modal UI displaying custom content for a selected patient. It can also create a new patient-related task when a specific endpoint is called, handling user authentication and returning appropriate UI and API responses. The flow is tailored to staff users interacting with patient charts.
    ```python
    import arrow
    from http import HTTPStatus
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.launch_modal import LaunchModalEffect
    from canvas_sdk.effects.simple_api import Response, JSONResponse, HTMLResponse
    from canvas_sdk.effects.task import AddTask, TaskStatus
    from canvas_sdk.handlers.application import Application
    from canvas_sdk.handlers.simple_api import StaffSessionAuthMixin, SimpleAPI, api
    from canvas_sdk.templates import render_to_string
    from canvas_sdk.v1.data.staff import Staff
    class MyChartApplication(Application):
        def on_open(self) -> Effect:
            patient_id = self.context['patient']['id']
            return LaunchModalEffect(
                url=f"/plugin-io/api/example_chart_app/custom-ui?patient={patient_id}",
                target=LaunchModalEffect.TargetType.RIGHT_CHART_PANE,
            ).apply()
    class MyApi(StaffSessionAuthMixin, SimpleAPI):
        @api.get("/custom-ui")
        def custom_ui(self) -> list[Response | Effect]:
            logged_in_staff = Staff.objects.get(id=self.request.headers["canvas-logged-in-user-id"])
            context = {
                "logged_in_staff": logged_in_staff,
                "patient_id": self.request.query_params["patient"],
            }
            return [
                HTMLResponse(
                    render_to_string("templates/custom-ui.html", context),
                    status_code=HTTPStatus.OK,
                )
            ]
        @api.post("/add-task")
        def add_task(self) -> list[Response | Effect]:
            add_task = AddTask(
                title="This came from the custom patient ui.",
                patient_id=self.request.json()["patient_id"],
                due=arrow.utcnow().shift(days=5).datetime,
                status=TaskStatus.OPEN,
            )
            return [
                add_task.apply(),
                JSONResponse(
                    {"message": "Task will be created"},
                    status_code=HTTPStatus.ACCEPTED
                )
            ]
    ```
##  assets/ 
###  rx.png 
This is an image file used as the Application icon within the Canvas UI.
----- END PAGE https://docs.canvasmedical.com/sdk/example-example_chart_app/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/example-extend_ai_pdf/
> **Note:** [View the source](https://github.com/canvas-medical/canvas-plugins/tree/main/example-plugins/extend_ai_pdf) for this plugin on GitHub. 
##  Description 
Plugin that provides a SimpleAPI for intelligent document processing using the Extend AI client. It supports listing processors, running document extraction on PDF files, checking run status, managing stored files, and retrieving processing results. Includes a chart application that renders a form interface for PDF processing directly from the chart.
##  Configuration 
This example plugin defines the following "secrets" in the manifest file:
    ```plaintext
        "variables": [
            {"name": "ExtendAiKey", "sensitive": true}
        ],
    ```
Once defined in the `MANIFEST.json`, set the secrets for your plugin in the Admin UI of your Canvas EMR. [Read more](https://docs.canvasmedical.com/sdk/secrets/)
###  ExtendAiKey 
Your Extend AI API key.
##  CANVAS_MANIFEST.json 
    ```json
    {
      "sdk_version": "0.81.0",
      "plugin_version": "0.0.1",
      "name": "pdf_manip",
      "description": "use extent.ai to extract information from a PDF document",
      "components": {
        "handlers": [
          {
            "class": "pdf_manip.handlers.pdf_manip:PdfManip",
            "description": "PDF extractor based on extent.ai"
          }
        ],
        "applications": [
          {
            "class": "pdf_manip.handlers.pdf_form_app:PdfFormApp",
            "name": "PDF Upload",
            "description": "Extend.ai manip",
            "icon": "static/pdf_manip.png",
            "scope": "patient_specific",
            "show_in_panel": false
          }
        ],
        "commands": [],
        "content": [],
        "effects": [],
        "views": []
      },
      "variables": [
        {"name": "ExtendAiKey", "sensitive": true}
      ],
      "tags": {},
      "references": [],
      "license": "",
      "diagram": false,
      "readme": "./README.md"
    }
    ```
##  handlers/ 
###  pdf_manip.py 
**Purpose**
This code defines a SimpleAPI handler that exposes REST endpoints for processing PDF documents using the Extend AI client from the Canvas SDK.
**Class Overview**
  - The main class, `PdfManip`, extends `StaffSessionAuthMixin` and `SimpleAPI`.
  - It creates an Extend AI client using an API key stored in plugin secrets.
**Main Workflow**
  - `GET /processors` — Lists all available Extend AI processors.
  - `GET /processors/<processor_id>` — Retrieves configuration for a specific processor.
  - `POST /execute` — Starts a processor run on a document from a public S3 URL.
  - `GET /status/<run_id>` — Checks the status of a run and cleans up files if completed.
  - `GET /result/<run_id>` — Retrieves the processing result for a completed run.
  - `GET /stored_files` — Lists all files stored in Extend AI.
  - `POST /delete_files` — Deletes specified files from Extend AI storage.
**Extend AI Client Integration**
  - The `_extend_client` method creates a `Client` instance from `canvas_sdk.clients.extend_ai.libraries`.
  - Error handling uses the `RequestFailed` exception from the Extend AI client structures.
    ```python
    from datetime import datetime
    from http import HTTPStatus
    from canvas_sdk.clients.extend_ai.constants import RunStatus, VersionName
    from canvas_sdk.clients.extend_ai.libraries import Client
    from canvas_sdk.clients.extend_ai.structures import RequestFailed
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.simple_api import JSONResponse, Response
    from canvas_sdk.handlers.simple_api import SimpleAPI, StaffSessionAuthMixin, api
    from pdf_manip.constants.secrets import Secrets
    class PdfManip(StaffSessionAuthMixin, SimpleAPI):
        """API handler for Extend AI PDF processing operations."""
        PREFIX = None
        USER_TYPE_STAFF = "Staff"
        def _extend_client(self) -> Client:
            """Create and return a configured Extend AI client."""
            return Client(self.secrets[Secrets.extend_ai_key])
        @api.get("/processors")
        def list_processors(self) -> list[Response | Effect]:
            """Retrieve all available Extend AI processors."""
            try:
                content: list | dict = [p.to_dict() for p in self._extend_client().list_processors()]
                status_code = HTTPStatus(HTTPStatus.OK)
            except RequestFailed as e:
                content = {"information": e.message}
                status_code = HTTPStatus(e.status_code)
            return [JSONResponse(content, status_code=status_code)]
        @api.get("/processors/<processor_id>")
        def get_processor(self) -> list[Response | Effect]:
            """Retrieve the configuration for a specific processor by ID."""
            try:
                processor_id = self.request.path_params["processor_id"]
                response = self._extend_client().processor(processor_id, VersionName.DRAFT.value)
                content = response.config.to_dict()
                status_code = HTTPStatus(HTTPStatus.OK)
            except RequestFailed as e:
                content = {"information": e.message}
                status_code = HTTPStatus(e.status_code)
            return [JSONResponse(content, status_code=status_code)]
        @api.get("/result/<run_id>")
        def run_result(self) -> list[Response | Effect]:
            """Retrieve the processing result for a completed run."""
            try:
                run_id = self.request.path_params["run_id"]
                response = self._extend_client().run_status(run_id)
                if response.status == RunStatus.PROCESSED:
                    content = {"result": response.output.to_dict()}
                    status_code = HTTPStatus(HTTPStatus.OK)
                else:
                    content = {"result": response.status}
                    status_code = HTTPStatus(HTTPStatus.UNPROCESSABLE_ENTITY)
            except RequestFailed as e:
                content = {"information": e.message}
                status_code = HTTPStatus(e.status_code)
            return [JSONResponse(content, status_code=status_code)]
        @api.get("/status/<run_id>")
        def run_status(self) -> list[Response | Effect]:
            """Check the status of a processor run and clean up files if completed."""
            try:
                run_id = self.request.path_params["run_id"]
                extend_ai = self._extend_client()
                response = extend_ai.run_status(run_id)
                if response.status == RunStatus.PROCESSED:
                    for file in response.files:
                        extend_ai.delete_file(file.id)
                content = {"runId": response.id, "status": response.status.value}
                status_code = HTTPStatus(HTTPStatus.OK)
            except RequestFailed as e:
                content = {"information": e.message}
                status_code = HTTPStatus(e.status_code)
            return [JSONResponse(content, status_code=status_code)]
        @api.get("/stored_files")
        def extend_stored_files(self) -> list[Response | Effect]:
            """List all files stored in Extend AI."""
            try:
                content: list | dict = [f.to_dict() for f in self._extend_client().list_files()]
                status_code = HTTPStatus(HTTPStatus.OK)
            except RequestFailed as e:
                content = {"information": e.message}
                status_code = HTTPStatus(e.status_code)
            return [JSONResponse(content, status_code=status_code)]
        @api.post("/delete_files")
        def extend_delete_files(self) -> list[Response | Effect]:
            """Delete specified files from Extend AI storage."""
            try:
                content: list | dict = [
                    {
                        "id": file_id,
                        "deleted": self._extend_client().delete_file(file_id),
                    }
                    for file_id in self.request.json().get("fileIds") or []
                ]
                status_code = HTTPStatus(HTTPStatus.OK)
            except RequestFailed as e:
                content = {"information": e.message}
                status_code = HTTPStatus(e.status_code)
            return [JSONResponse(content, status_code=status_code)]
        @api.post("/execute")
        def run_start(self) -> list[Response | Effect]:
            """Start a processor run on a document from a public S3 URL."""
            try:
                received = self.request.json()
                aws_s3_url = received.get("fileAwsS3Url")
                processor_id = received.get("processorId")
                response = self._extend_client().run_processor(
                    processor_id=processor_id,
                    file_name=f"processed-{datetime.now().isoformat(timespec='seconds')}",
                    file_url=aws_s3_url,
                    config=None,
                )
                content = {"runId": response.id, "status": response.status.value}
                status_code = HTTPStatus(HTTPStatus.OK)
            except RequestFailed as e:
                content = {"information": e.message}
                status_code = HTTPStatus(e.status_code)
            return [JSONResponse(content, status_code=status_code)]
    ```
###  pdf_form_app.py 
**Purpose**
This code defines an Application handler that launches a modal form in the right chart pane for interacting with the Extend AI PDF processing API endpoints.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.launch_modal import LaunchModalEffect
    from canvas_sdk.handlers.application import Application
    from canvas_sdk.templates import render_to_string
    class PdfFormApp(Application):
        """Application handler that displays the PDF processing form in a modal."""
        PLUGIN_API_BASE_ROUTE = "/plugin-io/api/pdf_manip"
        def on_open(self) -> Effect:
            """Render and launch the PDF processing form modal in the right chart pane."""
            content = render_to_string(
                "templates/pdf_form.html",
                {
                    "processorsURL": f"{self.PLUGIN_API_BASE_ROUTE}/processors",
                    "statusURL": f"{self.PLUGIN_API_BASE_ROUTE}/status",
                    "executeURL": f"{self.PLUGIN_API_BASE_ROUTE}/execute",
                    "resultURL": f"{self.PLUGIN_API_BASE_ROUTE}/result",
                    "storedFilesURL": f"{self.PLUGIN_API_BASE_ROUTE}/stored_files",
                    "deleteFilesURL": f"{self.PLUGIN_API_BASE_ROUTE}/delete_files",
                },
            )
            return LaunchModalEffect(
                content=content,
                target=LaunchModalEffect.TargetType.RIGHT_CHART_PANE,
            ).apply()
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/example-extend_ai_pdf/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/example-llm/
> **Note:** [View the source](https://github.com/canvas-medical/canvas-plugins/tree/main/example-plugins/llm) for this plugin on GitHub. 
##  Description 
Plugin that provides a SimpleAPI for interacting with multiple LLM providers (Anthropic, Google, OpenAI) through the Canvas SDK's unified LLM client. It supports image analysis with structured JSON output, multi-turn chat conversations, and file content analysis. Includes a chart application that renders a form interface for LLM interactions directly from the chart.
##  Configuration 
This example plugin defines the following "secrets" in the manifest file:
    ```plaintext
        "variables": [
            {"name": "AnthropicKey", "sensitive": true},
            {"name": "GoogleKey", "sensitive": true},
            {"name": "OpenaiKey", "sensitive": true}
        ],
    ```
Once defined in the `MANIFEST.json`, set the secrets for your plugin in the Admin UI of your Canvas EMR. [Read more](https://docs.canvasmedical.com/sdk/secrets/)
###  AnthropicKey 
Your [Anthropic API key](https://console.anthropic.com/settings/keys).
###  GoogleKey 
Your [Google AI API key](https://aistudio.google.com/apikey).
###  OpenaiKey 
Your [OpenAI API key](https://platform.openai.com/api-keys).
##  CANVAS_MANIFEST.json 
    ```json
    {
      "sdk_version": "0.81.0",
      "plugin_version": "0.0.1",
      "name": "llm_manip",
      "description": "use LLM to interact with the user",
      "components": {
        "handlers": [
          {
            "class": "llm_manip.handlers.llm_manip:LlmManip",
            "description": "LLM communication wrapper"
          }
        ],
        "applications": [
          {
            "class": "llm_manip.handlers.llm_form_app:LlmFormApp",
            "name": "LLM Interactions",
            "description": "LLM interactions with the user",
            "icon": "static/llm_manip.png",
            "scope": "patient_specific",
            "show_in_panel": false
          }
        ],
        "commands": [],
        "content": [],
        "effects": [],
        "views": []
      },
      "variables": [
        {"name": "AnthropicKey", "sensitive": true},
        {"name": "GoogleKey", "sensitive": true},
        {"name": "OpenaiKey", "sensitive": true}
      ],
      "tags": {},
      "references": [],
      "license": "",
      "diagram": false,
      "readme": "./README.md"
    }
    ```
##  handlers/ 
###  llm_manip.py 
**Purpose**
This code defines a SimpleAPI handler that exposes REST endpoints for interacting with LLM providers using the Canvas SDK's unified LLM client.
**Class Overview**
  - The main class, `LlmManip`, extends `SimpleAPI`.
  - It supports three LLM providers: Anthropic (Claude), Google (Gemini), and OpenAI (GPT Models).
  - It demonstrates structured JSON output using Pydantic models (`LlmResponse`, `Result`).
**Main Workflow**
  - `POST /animals_count/<llm_provider>` — Analyzes an image URL to count animals using LLM vision capabilities with structured JSON output.
  - `POST /chat/<llm_provider>` — Processes a multi-turn chat conversation with system, user, and model roles.
  - `POST /file/<llm_provider>` — Analyzes uploaded file content using multipart form data.
**LLM Client Integration**
  - The `_llm_client` method creates provider-specific clients (`LlmAnthropic`, `LlmGoogle`, `LlmOpenai`) with their respective settings classes.
  - Structured output is configured via `client.set_schema()` with Pydantic models extending `BaseModelLlmJson`.
  - File attachments are supported via `LlmFileUrl` for URLs and `FileContent` for binary content.
    ```python
    import base64
    from http import HTTPStatus
    from pydantic import Field
    from canvas_sdk.clients.llms.constants import FileType
    from canvas_sdk.clients.llms.libraries import LlmAnthropic, LlmApi, LlmGoogle, LlmOpenai
    from canvas_sdk.clients.llms.structures import BaseModelLlmJson, FileContent, LlmFileUrl
    from canvas_sdk.clients.llms.structures.settings import (
        LlmSettingsAnthropic,
        LlmSettingsGemini,
        LlmSettingsGpt4,
    )
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.simple_api import JSONResponse, PlainTextResponse, Response
    from canvas_sdk.handlers.simple_api import Credentials, SimpleAPI, api
    from canvas_sdk.handlers.simple_api.api import FileFormPart, StringFormPart
    from llm_manip.constants.secrets import Secrets
    class Result(BaseModelLlmJson):
        """Structured response model for animal counting results."""
        count_dogs: int = Field(description="the number of dogs")
        count_cats: int = Field(description="the number of cats")
        count_total: int = Field(description="the number of animals")
    class LlmResponse(BaseModelLlmJson):
        """Structured response model for LLM animal analysis with optional result."""
        comment: str = Field(description="the comment")
        result: Result | None
    class LlmManip(SimpleAPI):
        """Simple API handler for LLM-based image analysis and chat operations."""
        PREFIX = None
        LLM_ANTHROPIC = 0
        LLM_GOOGLE = 1
        LLM_OPENAI = 2
        def authenticate(self, credentials: Credentials) -> bool:
            """Authenticate API requests."""
            return True
        def _llm_client(self, provider: int) -> LlmApi:
            """Create and configure a LLM client with credentials from secrets."""
            if provider == self.LLM_ANTHROPIC:
                return LlmAnthropic(
                    LlmSettingsAnthropic(
                        api_key=self.secrets[Secrets.anthropic_key],
                        model="claude-sonnet-4-5",
                        temperature=1.0,
                        max_tokens=8192,
                    )
                )
            elif provider == self.LLM_GOOGLE:
                return (
                    LlmGoogle(
                        LlmSettingsGemini(
                            api_key=self.secrets[Secrets.anthropic_key],
                            model="models/gemini-2.5-flash",
                            temperature=1.0,
                        )
                    ),
                )
            else:
                return LlmOpenai(
                    LlmSettingsGpt4(
                        api_key=self.secrets[Secrets.openai_key],
                        model="gpt-4o",
                        temperature=2.0,
                    )
                )
        @api.post("/animals_count/<llm_provider>")
        def animals_count(self) -> list[Response | Effect]:
            """Analyze an image URL to count animals using LLM vision capabilities."""
            client = self._llm_client(self.request.path_params["llm_provider"])
            url = self.request.json().get("url")
            if not url:
                url = "https://images.unsplash.com/photo-1563460716037-460a3ad24ba9?w=125"
            client.set_schema(LlmResponse)
            client.set_system_prompt(
                ["Your task is to read the pictures provided by the user and count the animals in it."]
            )
            client.set_user_prompt(["Identify the content of the provided picture."])
            client.add_url_file(LlmFileUrl(url=url, type=FileType.IMAGE))
            responses = client.attempt_requests(attempts=2)
            content = [r.to_dict() for r in responses]
            return [JSONResponse(content, status_code=HTTPStatus(HTTPStatus.OK))]
        @api.post("/chat/<llm_provider>")
        def chat(self) -> list[Response | Effect]:
            """Process a multi-turn chat conversation with the LLM."""
            client = self._llm_client(self.request.path_params["llm_provider"])
            for turn in self.request.json():
                if not isinstance(turn, dict):
                    continue
                if turn.get("role") == "system":
                    client.set_system_prompt([turn.get("prompt", "")])
                elif turn.get("role") == "user":
                    client.set_user_prompt([turn.get("prompt", "")])
                else:
                    client.set_model_prompt([turn.get("prompt", "")])
            response = client.attempt_requests(attempts=1)[0]
            return [PlainTextResponse(response.response, status_code=response.code)]
        @api.post("/file/<llm_provider>")
        def file(self) -> list[Response | Effect]:
            """Analyze file content using LLM."""
            content = b""
            mime_type = ""
            user_input = ""
            form_data = self.request.form_data()
            if "file" in form_data and isinstance(form_data["file"], FileFormPart):
                content = form_data["file"].content
                mime_type = form_data["file"].content_type
            if "input" in form_data and isinstance(form_data["input"], StringFormPart):
                user_input = form_data["input"].value
            if not (content and mime_type and user_input):
                return [PlainTextResponse("nothing to do", status_code=HTTPStatus(HTTPStatus.OK))]
            client = self._llm_client(self.request.path_params["llm_provider"])
            file = FileContent(
                mime_type=mime_type,
                content=base64.b64encode(content),
                size=len(content),
            )
            client.file_contents.append(file)
            client.set_system_prompt(["Answer to the question about the file, clearly and concisely."])
            client.set_user_prompt([user_input or "what is in the file?"])
            response = client.attempt_requests(attempts=1)[0]
            return [PlainTextResponse(response.response, status_code=response.code)]
    ```
###  llm_form_app.py 
**Purpose**
This code defines an Application handler that launches a modal form in the right chart pane for interacting with the LLM API endpoints.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.launch_modal import LaunchModalEffect
    from canvas_sdk.handlers.application import Application
    from canvas_sdk.templates import render_to_string
    class LlmFormApp(Application):
        """Application handler for launching the LLM interaction form interface."""
        PLUGIN_API_BASE_ROUTE = "/plugin-io/api/llm_manip"
        def on_open(self) -> Effect:
            """Render and launch the LLM interaction modal form."""
            content = render_to_string(
                "templates/llm_form.html",
                {
                    "animalsCountURL": f"{self.PLUGIN_API_BASE_ROUTE}/animals_count",
                    "chatURL": f"{self.PLUGIN_API_BASE_ROUTE}/chat",
                    "fileURL": f"{self.PLUGIN_API_BASE_ROUTE}/file",
                },
            )
            return LaunchModalEffect(
                content=content,
                target=LaunchModalEffect.TargetType.RIGHT_CHART_PANE,
            ).apply()
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/example-llm/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/example-patient_creation_platform_sync/
> **Note:** [View the source](https://github.com/canvas-medical/canvas-plugins/tree/main/example-plugins/example_patient_sync) for this plugin on GitHub. 
##  Description 
An example of bidirectional patient creation between Canvas and a 3rd party system.
At a high level, this plugin:
  1. Adds an API endpoint to which the external system can POST a new patient object with a given system_id, which creates a patient in Canvas with an external_identifier.
  2. Configures a webhook for the PATIENT_CREATED event to automatically synchronize patient data. When a patient is created in Canvas, the webhook triggers an update (or creation) in the external system via its patient GET/POST/PATCH API, ensuring the Canvas ID is always included.
##  Sample CURL request 
Once the API endpoint POST action is created, test it is working with the following CURL command (or use a popular GUI like Postman or Bruno). Replace "training" with your Canvas instance name:
    ```plaintext
    curl --request POST \
      --url https://training.canvasmedical.com/plugin-io/api/example_patient_sync/patients \
      --header 'content-type: application/json' \
      --header 'authorization: 97f2a0f033666d29ff09ee42b3afd7e4'
      --data '{
      "firstName": "Alice",
      "lastName": "Example",
      "sexAtBirth": "F",
      "dateOfBirth": "1980-02-22",
      "partnerId": "pat_12345678"
    }'
    ```
##  Defining and Setting Secrets 
This example plugin defines four "secrets" in the manifest file:
    ```plaintext
        "variables": [
            {"name": "PARTNER_URL_BASE", "sensitive": false},
            {"name": "PARTNER_API_BASE_URL", "sensitive": false},
            {"name": "PARTNER_SECRET_API_KEY", "sensitive": true},
            {"name": "simpleapi-api-key", "sensitive": true}
        ],
    ```
Once defined in the `MANIFEST.json`, set the secrets for your plugin in the Admin UI of your Canvas EMR. [Read more](https://docs.canvasmedical.com/sdk/secrets/)
###  PARTNER_URL_BASE 
This string value will be set as the "system" in the patient external identifier that is created. A patient can have many external identifiers, so make sure this is a unique value that is searchable to find their external ID in the future. [Read more](https://docs.canvasmedical.com/sdk/effect-create-patient-external-identifier/)
###  PARTNER_API_BASE_URL 
This string value will be used when making REST API calls to a partner system (to create or update a patient record, for example). It may or may not be the same as the PARTNER_URL_BASE.
###  PARTNER_SECRET_API_KEY 
If accessing a partner API requires authorization, this can define the auth secret to enable the API handshake.
###  simpleapi-api-key 
This is the authorization needed for Canvas when using APIKeyAuthMixin. [Read more](https://docs.canvasmedical.com/sdk/handlers-simple-api-http/#session)
##  CANVAS_MANIFEST.json 
    ```json
    {
        "sdk_version": "0.40.0",
        "plugin_version": "0.0.1",
        "name": "example_patient_sync",
        "description": "Example bidirectional patient synchronization between Canvas and a 3rd party system",
        "components": {
            "handlers": [
                {
                    "class": "example_patient_sync.handlers.patient_sync:PatientSync",
                    "description": "Create or update patients in an external system based on Canvas events",
                },
                {
                    "class": "example_patient_sync.routes.patient_create_api:PatientCreateApi",
                    "description": "Create a patient in Canvas when a user is created in an external system",
                }
            ],
            "commands": [],
            "content": [],
            "effects": [],
            "views": []
        },
        "variables": [
            {"name": "PARTNER_URL_BASE", "sensitive": false},
            {"name": "PARTNER_API_BASE_URL", "sensitive": false},
            {"name": "PARTNER_SECRET_API_KEY", "sensitive": true},
            {"name": "simpleapi-api-key", "sensitive": true}
        ],
        "tags": {},
        "references": [],
        "license": "",
        "diagram": false,
        "readme": "./README.md"
    }
    ```
##  routes/ 
###  patient_create_api.py 
**Purpose**
The code defines an HTTP API endpoint for creating new patient records in Canvas from a third-party system. It authenticates requests using an API key and expects data in JSON format.
**Authentication**
The endpoint uses APIKeyAuthMixin, which requires clients to provide a valid API key in the request headers for authorization.
**Endpoint Details**
  - **Route:** POST /patients
  - **Description:** Accepts and processes patient creation requests sent by external systems.
**Request Handling**
  - Expects a JSON body describing the patient. If the JSON body is not a dictionary, returns a "400 Bad Request".
  - Extracts and processes the following fields from the request: 
    - `firstName`: Patient's first name.
    - `lastName`: Patient's last name.
    - `dateOfBirth`: Patient's date of birth, parsed and converted to a date object.
    - `sexAtBirth`: Patient's sex at birth; attempts to standardize and map to one of the PersonSex enum values (`SEX_FEMALE`, `SEX_MALE`, `SEX_OTHER`, `SEX_UNKNOWN`). Unrecognized values are ignored.
    - `partnerId`: An external identifier for the patient, which is combined with a secret value (`PARTNER_URL_BASE`) to create a unique identifier object.
**Patient Creation**
  - Constructs a Patient object with the collected and parsed data.
  - Attaches the external identifier from the requesting system.
**Response**
  - Issues two actions in its response list: 
    - Requests the creation of the Patient record (an Effect for persistence in Canvas).
    - Returns a JSON response (HTTP 202 Accepted) containing the external identifier information (system and value), indicating the request was accepted.
**Error Handling**
  - If the request body is not valid JSON or not a dictionary, the endpoint immediately returns a 400 Bad Request with an error message.
**Dependencies**
  - Relies on the Canvas SDK for authentication, response creation, effect handling, and data models.
  - Uses the arrow library for robust date parsing.
    ```python
    from http import HTTPStatus
    from typing import cast
    import arrow
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.patient import Patient, PatientExternalIdentifier
    from canvas_sdk.effects.simple_api import JSONResponse, Response
    from canvas_sdk.handlers.simple_api import APIKeyAuthMixin, SimpleAPI, api
    from canvas_sdk.v1.data.common import PersonSex
    # Authentication is handled by the APIKeyAuthMixin, which checks the API key in the request headers
    # https://docs.canvasmedical.com/sdk/handlers-simple-api-http/#api-key-1
    class PatientCreateApi(APIKeyAuthMixin, SimpleAPI):
        """API endpoint for use by third-party system to create patients in Canvas when that system is the point of origination for that patient record."""
        # Docs: https://docs.canvasmedical.com/sdk/handlers-simple-api-http/
        # POST https://<instance-name>.canvasmedical.com/plugin-io/api/example_patient_sync/patients
        @api.post("/patients")
        def post(self) -> list[Response | Effect]:
            """Handle POST requests for patient sync."""
            json_body = self.request.json()
            if not isinstance(json_body, dict):
                return [
                    JSONResponse(
                        content="Invalid JSON body.", status_code=HTTPStatus.BAD_REQUEST
                    ).apply()
                ]
            birthdate = None
            date_of_birth_str = json_body.get("dateOfBirth")
            if isinstance(date_of_birth_str, str) and date_of_birth_str:
                birthdate = arrow.get(date_of_birth_str).date()
            sex_at_birth = None
            sex_at_birth_str = json_body.get("sexAtBirth")
            if sex_at_birth_str:
                s = cast(str, sex_at_birth_str).strip().upper()
                if s in ("F", "FEMALE"):
                    sex_at_birth = PersonSex.SEX_FEMALE
                elif s in ("M", "MALE"):
                    sex_at_birth = PersonSex.SEX_MALE
                elif s in ("O", "OTHER"):
                    sex_at_birth = PersonSex.SEX_OTHER
                elif s in ("U", "UNKNOWN"):
                    sex_at_birth = PersonSex.SEX_UNKNOWN
                else:
                    sex_at_birth = None
            partner_id = str(json_body.get("partnerId"))
            external_id = PatientExternalIdentifier(
                system=self.secrets['PARTNER_URL_BASE'],
                value=partner_id,
            )
            patient = Patient(
                birthdate=birthdate,
                first_name=str(json_body.get("firstName")),
                last_name=str(json_body.get("lastName")),
                sex_at_birth=sex_at_birth,
                external_identifiers=[external_id],
            )
            response = {"external_identifier": {"system": self.secrets['PARTNER_URL_BASE'], "value": partner_id}}
            return [
                patient.create(),
                JSONResponse(content=response, status_code=HTTPStatus.ACCEPTED).apply(),
            ]
    ```
##  handlers/ 
###  patient_sync.py 
**Summary**
This file defines a synchronization handler for patient data between the Canvas Medical platform and an external partner system. It listens for the event when a patient is created on Canvas and then ensures that this patient also exists (and is linked via an external identifier) in the partner system. The handler manages bidirectional lookup and updates of patient IDs between the two systems.
**Details of Operation**
  - The core class, `PatientSync`, inherits from `BaseHandler` and is triggered by the `PATIENT_CREATED` event in Canvas.
  - It uses configuration secrets and environment variables to determine endpoints, authentication headers, and other partner-specific parameters.
  - When a new patient is created in Canvas, the handler: 
    1. Checks if the patient already has an external identifier linking them to the partner system.
    2. If not, attempts to look up the patient in the partner system using the Canvas patient ID by issuing a GET request.
    3. If an existing patient ID is found in the partner system, or after determining a new patient needs to be created, it prepares a payload with patient details.
    4. Issues a POST request to the partner API to either create or update the patient there.
    5. If creating, the handler expects the partner system to return the newly created patient's external ID.
    6. Handles duplicate creation attempts (HTTP 409) by simply returning without making changes.
    7. When a new external identifier is obtained, triggers an Effect to update the Canvas patient record with this identifier.
**Technical Implementation**
  - Secrets such as API keys and base URLs are used for secure access and configuration.
  - The handler functions as a stateless, event-driven adapter to maintain patient cross-system consistency.
  - HTTP request functionality is abstracted via Canvas SDK utilities.
  - Effects (a pattern used in Canvas plugins) are queued to perform changes in Canvas asynchronously and safely.
  - Logging is imported but not shown in use in the provided code.
**Key Methods**
  - `lookup_external_id_by_system_url`: Searches Canvas patient external identifiers for the partner system's record.
  - `get_patient_from_system_api`: Fetches patient details from the external partner via GET request.
  - `compute`: Main workflow coordinating the above logic, handling deduplication, updates, and creation logic for patients in both systems.
**Error and Edge Cases**
  - Handles the situation where the partner system returns a duplicate on creation.
  - Deals with missing IDs by attempting lookups and only initiating creation when necessary.
  - Only updates Canvas when a new cross-system ID must be set.
**Purpose**
This plugin component ensures that every patient created in Canvas is also represented in a connected partner system, with persistent, synchronized external identifiers to enable cross-platform interoperability and data consistency.
    ```python
    from typing import Any
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.patient import CreatePatientExternalIdentifier
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers.base import BaseHandler
    from canvas_sdk.utils import Http
    from canvas_sdk.v1.data.patient import Patient
    from logger import log
    class PatientSync(BaseHandler):
        """Handler for synchronizing patient data between systems."""
        RESPONDS_TO = [
            EventType.Name(EventType.PATIENT_CREATED),
        ]
        @property
        def partner_url_base(self) -> str:
            """Return the base URL for the external partner platform."""
            return self.secrets["PARTNER_URL_BASE"]
        def partner_api_base_url(self) -> str:
            """Return the base URL for the external partner API."""
            return self.secrets["PARTNER_API_BASE_URL"]
        @property
        def partner_request_headers(self) -> dict[str, str]:
            """Return the request headers for external partner API requests."""
            partner_secret_api_key = self.secrets["PARTNER_SECRET_API_KEY"]
            return {"X-API-Key": partner_secret_api_key}
        @property
        def partner_patient_metadata(self) -> Any:
            """Return metadata for creation of the patient on external partner platform."""
            metadata = {"canvasPatientId": self.target}
            subdomain = self.environment["CUSTOMER_IDENTIFIER"]
            canvas_url = f"https://{subdomain}.canvasmedical.com"
            # This sets the canvas URL for the patient in the partner platform metadata
            # Combined with the canvasPatientId, this allows the partner platform to link back to the patient in Canvas
            metadata["canvasUrl"] = canvas_url
            return metadata
        def lookup_external_id_by_system_url(self, canvas_patient: Patient, system: str) -> str | None:
            """Get the system ID for a given patient and system from Canvas."""
            # If the patient already has a external identifier for the partner platform, identified by a matching system url, use the first one
            return (
                canvas_patient.external_identifiers.filter(system=system)
                .values_list("value", flat=True)
                .first()
            )
        def get_patient_from_system_api(self, canvas_patient_id: str) -> Any:
            """Look up a patient in the external system."""
            http = Http()
            return http.get(
                f"{self.partner_api_base_url}/patients/v2/{canvas_patient_id}",
                headers=self.partner_request_headers,
            )
        def compute(self) -> list[Effect]:
            """Compute the sync actions for the patient."""
            canvas_patient_id = self.target
            http = Http()
            canvas_patient = Patient.objects.get(id=canvas_patient_id)
            # by default assume we don't yet have a system patient ID
            # and that we need to update the patient in Canvas to add one
            system_patient_id = self.lookup_external_id_by_system_url(canvas_patient, self.partner_url_base)
            update_patient_external_identifier = system_patient_id is None
            # Here we check if the patient already has an external ID in Canvas for the partner platform
            if not system_patient_id:
                # Get the system external ID by making a GET request to the partner platform
                system_patient = self.get_patient_from_system_api(canvas_patient_id)
                system_patient_id = (
                    system_patient.json()["id"] if system_patient.status_code == 200 else None
                )
            # Great, now we know if the patient is assigned a system external ID with the partner
            # platform, and if we need to update it. At this point the system_patient_id can be 3 possible values:
            # 1. value we already had stored in Canvas in an external identifier,
            # 2. value we just got from our partner GET API lookup, or
            # 3. None
            # And we have a true/false call to action telling us if we need to add
            # an external identifier to our Canvas patient: `update_patient_external_identifier`
            # Generate the payload for creating or updating the patient in partner platform API
            partner_payload = {
                "externalId": canvas_patient.id,
                "firstName": canvas_patient.first_name,
                "lastName": canvas_patient.last_name,
                "dateOfBirth": canvas_patient.birth_date.isoformat(),
            }
            base_request_url = f"{self.partner_api_base_url}/patients/v2"
            # If we have a patient's partner external id, we know this is an update, so we'll append it to the request URL
            request_url = (
                f"{base_request_url}/{system_patient_id}" if system_patient_id else base_request_url
            )
            resp = http.post(request_url, json=partner_payload, headers=self.partner_request_headers)
            # If your system's API returns the ID of the newly created record,
            # grab it from the response so we can add it to the Canvas patient record
            if system_patient_id is None:
                system_patient_id = resp.json().get("id")
            duplicate_patient_attempt = resp.status_code == 409
            if duplicate_patient_attempt:
                # If your system's API can let you know when a duplicate record was attempted to be added,
                # you can use that information to return early here.
                return []
            elif update_patient_external_identifier:
                # Queue up an effect to update the patient in Canvas and add the external identifier
                external_id = CreatePatientExternalIdentifier(
                    patient_id=canvas_patient.id,
                    system=self.partner_url_base,
                    value=str(system_patient_id)
                )
                return [external_id.create()]
            else:
                return [] # Done!
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/example-patient_creation_platform_sync/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/example-patient_summary_chart_groups/
> **Note:** [View the source](https://github.com/canvas-medical/canvas-plugins/tree/main/example-plugins/patient_summary_chart_groups) for this plugin on GitHub. 
A plugin that groups Psychiatry conditions and medications in the patient summary chart.
##  SDK Features 
  - Responds to `PATIENT_CHART__CONDITIONS` and `PATIENT_CHART__MEDICATIONS` [events](/sdk/events/#patient-chart-configuration)
  - Loads the patient conditions and medications using event context
  - Iterates through conditions to retrieve the ICD10 CodeSystem and assigns to a [Group effect](/sdk/patient-chart-group-effect/#group) list of items
  - Iterates through medications to find an RxNorm match on a list of plugin-defined medication codes and assigns to a [Group effect](/sdk/patient-chart-group-effect/#group) list of items
  - Returns the [`PatientChartGroup` effect](/sdk/patient-chart-group-effect/) with `.apply()` called
##  CANVAS_MANIFEST.json 
    ```json
    {
        "sdk_version": "0.1.4",
        "plugin_version": "0.0.1",
        "name": "patient_summary_chart_groups",
        "description": "A plugin that groups Psychiatry conditions and medications in the patient summary chart.",
        "components": {
            "handlers": [
                {
                    "class": "patient_summary_chart_groups.handlers.my_handler:Conditions",
                    "description": "A handler that groups Psychiatry conditions"
                },
                {
                    "class": "patient_summary_chart_groups.handlers.my_handler:Medications",
                    "description": "A handler that groups Psychiatry medications"
                }
            ],
            "commands": [],
            "content": [],
            "effects": [],
            "views": []
        },
        "variables": [],
        "tags": {},
        "references": [],
        "license": "",
        "diagram": false,
        "readme": "./README.md"
    }
    ```
##  handlers/ 
###  my_handler.py 
This file defines two custom handlers, `Conditions` and `Medications`, using the Canvas SDK. These handlers listen for specific events related to a patient's medical chart and group relevant diagnoses or medications into a "Psychiatry" category, returning these as effects for further processing or display in the Canvas UI.
**Section:`Conditions` Handler**
  - Inherits from `BaseHandler`.
  - Listens to the `EventType.PATIENT_CHART__CONDITIONS` event (triggered when a patient's chart conditions are accessed).
  - For each condition in the event context, it looks at the `codings` list: 
    - If the coding system is ICD-10 (`CodeSystems.ICD10`) and the code falls within the ICD-10 psychiatry range ("F01" to "F99") or starts with "R45." (indicating certain psychiatric symptoms), the condition is added to a group named "Psychiatry".
  - The resulting group is returned using a `PatientChartGroup` effect, applied to be consumed by other parts of the platform (such as UI rendering).
**Section:`Medications` Handler**
  - Inherits from `BaseHandler`.
  - Listens to the `EventType.PATIENT_CHART__MEDICATIONS` event (triggered when a patient's medication list is accessed).
  - Maintains an explicit list of RxNorm codes (`medication_codes`) representing psychiatric or relevant medications.
  - For each medication in the event context, it looks at the `codings` list: 
    - If the coding system is RxNorm (`CodeSystems.RXNORM`) and the code (converted to integer) is in the handler's medication list, the medication is added to the "Psychiatry" group.
  - The resulting group is returned using a `PatientChartGroup` effect, again allowing psychiatric medications to be grouped in the UI.
**Section: Canvas SDK Constructs Used**
  - **Events:** Listens for specific event types (patient chart conditions or medications).
  - **Effects:** Returns grouping instructions as `PatientChartGroup` effects, making downstream processing or UI changes possible.
  - **Grouping:** Uses the `Group` object to label and prioritize (with `priority=100`) psychiatric items, and the `PatientChartGroup` to wrap effect logic for patient chart groupings.
**Section: Implementation Details**
  - The handlers use dictionary storage for groups but always construct only a single "Psychiatry" group.
  - ICD-10 and RxNorm code checks strictly conform to predefined domain ranges or explicit code lists.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.group import Group
    from canvas_sdk.effects.patient_chart_group import PatientChartGroup
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.commands.constants import CodeSystems
    class Conditions(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.PATIENT_CHART__CONDITIONS)
        def compute(self) -> list[Effect]:
            groups: dict[str, Group] = {}
            groups.setdefault("Psychiatry", Group(priority=100, items=[], name="Psychiatry"))
            for condition in self.event.context:
               for coding in condition["codings"]:
                   if coding["system"] == CodeSystems.ICD10 and ("F01" <= coding["code"] <= "F99" or coding["code"].startswith("R45.")):
                       groups["Psychiatry"].items.append(condition)
                       break
            return [PatientChartGroup(items=groups).apply()]
    class Medications(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.PATIENT_CHART__MEDICATIONS)
        medication_codes = [
            70223, 725, 17941, 1359, 89781, 314517, 1422, 42347, 1841, 3007, 2591497, 62174, 22656, 352372, 3288, 1598,
            3389, 3415, 3416, 3417, 3554, 10402, 11636, 1653781, 4024, 14584, 4328, 325526, 4850, 6130, 6373, 700810, 6664,
            6694, 6782, 6816, 6901, 7243, 326374, 31994, 7781, 2387302, 7966, 33272, 8133, 8150, 8152, 8702, 8704, 9260,
            36514, 1547099, 10689, 11289, 68503, 3920, 4457, 237005, 6378, 6754, 7814, 8001, 9601, 3264, 7242, 7895, 237099,
            203223, 3554, 2626143, 6711, 7407, 314875, 11256, 82819, 161203, 16735, 272, 2557217, 237099, 596, 704, 719,
            722, 725, 17381, 89013, 1673265, 641465, 784649, 38400, 1291301, 1292, 1373, 2690627, 2176312, 2121777, 1658314,
            1749, 19759, 19777, 1819, 42347, 1827, 477631, 19874, 2002, 1667655, 2296, 2356, 2372, 2373, 2403, 2406, 2556,
            2597, 2598, 2599, 2603, 2353, 2622, 2626, 3013, 2591497, 62174, 3247, 3251, 734064, 352372, 3288, 3322, 3332,
            91235, 3403, 3407, 203223, 3498, 3554, 2687966, 135447, 3634, 3637, 3638, 3642, 3648, 72625, 3755, 4024, 321988,
            2119365, 4077, 461016, 4118, 24474, 4328, 1665509, 4457, 4460, 4493, 4495, 4496, 4501, 4507, 42355, 25480, 4637,
            2672253, 325526, 4903, 40114, 26412, 5093, 2267703, 5553, 73178, 5691, 5975, 262150, 6011, 6026, 285228, 28439,
            2626143, 2272403, 237005, 1433212, 700810, 6448, 746070, 42351, 52105, 1546376, 28863, 6470, 28894, 6475,
            2275602, 1040028, 52356, 6646, 6673, 6680, 6711, 6719, 6760, 6779, 6813, 6816, 6823, 6852, 6901, 6904, 6910,
            6960, 588250, 15996, 30125, 7019, 31479, 7242, 7243, 31565, 7407, 7440, 7486, 3155, 7531, 61381, 26225, 1370971,
            7781, 2387302, 679314, 7895, 7909, 32937, 7966, 7974, 8042, 8047, 8766, 8076, 8123, 8156, 8331, 33739, 8338,
            8348, 2197878, 34345, 746741, 3143, 8627, 8701, 8704, 8742, 8770, 8782, 8787, 8825, 8826, 8886, 35185, 51272,
            9100, 596205, 60842, 9260, 35636, 183379, 616739, 2559612, 9624, 9639, 2562176, 41996, 36437, 36676, 1547099,
            10318, 1490468, 10355, 10390, 37985, 10437, 10454, 10464, 38077, 10502, 10510, 31914, 38260, 314875, 38365,
            38404, 10734, 10737, 10767, 1314420, 10800, 10804, 10805, 10834, 10898, 21406, 11017, 1665222, 253206, 40254,
            39786, 1086769, 11256, 1455099, 2694828, 220982, 74667, 115698, 39993, 2669905
        ]
        def compute(self) -> list[Effect]:
            groups: dict[str, Group] = {}
            groups.setdefault("Psychiatry", Group(priority=100, items=[], name="Psychiatry"))
            for medication in self.event.context:
                for coding in medication["codings"]:
                    if coding["system"] == CodeSystems.RXNORM and int(coding["code"]) in self.medication_codes:
                        groups["Psychiatry"].items.append(medication)
                        break
            return [PatientChartGroup(items=groups).apply()]
    ```
##  Customize 
  - The handler is extensible: additional groups or more refined code logic could be added in the future.   
----- END PAGE https://docs.canvasmedical.com/sdk/example-patient_summary_chart_groups/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/example-paytheory_payment_processor/
> **Note:** [View the source](https://github.com/Medical-Software-Foundation/canvas/tree/main/extensions/paytheory-payment-processor) for this plugin on GitHub. 
This Canvas EMR plugin integrates [Pay Theory](https://paytheory.com) as a custom credit card [payment processor](/sdk/handlers-payment-processors/), letting Canvas collect payments and manage a patient's saved cards through a third-party provider instead of the built-in Stripe integration.
##  SDK Features 
  - Extends the [`CardPaymentProcessor`](/sdk/handlers-payment-processors/#cardpaymentprocessor) handler
  - Responds to the [`REVENUE__PAYMENT_PROCESSOR__*` events](/sdk/events/#payment-processor-events)
  - Renders tokenization forms with the [`PaymentProcessorForm`](/sdk/payment-processor-effect/#paymentprocessorform) effect and [Django templates](/sdk/layout-effect/#custom-html-and-django-templates)
  - Charges cards and returns a [`CardTransaction`](/sdk/payment-processor-effect/#cardtransaction) effect
  - Lists, adds, and removes saved cards with the [`PaymentMethod`](/sdk/payment-processor-effect/#paymentmethod), [`AddPaymentMethodResponse`](/sdk/payment-processor-effect/#addpaymentmethodresponse), and [`RemovePaymentMethodResponse`](/sdk/payment-processor-effect/#removepaymentmethodresponse) effects
  - Reads configuration from plugin [variables](/sdk/secrets/) and declares `url_permissions` for the Pay Theory JS SDK
##  CANVAS_MANIFEST.json 
    ```json
    {
        "sdk_version": "0.1.4",
        "plugin_version": "0.0.1",
        "name": "paytheory_payment_processor",
        "description": "PayTheory credit card payment processor integration",
        "url_permissions": [
            {
                "url": "https://*.sdk.paytheory.com/index.js",
                "permissions": ["SCRIPTS"]
            }
        ],
        "components": {
            "handlers": [
                {
                    "class": "paytheory_payment_processor.handlers.paytheory_payment_processor:PayTheoryPaymentProcessor",
                    "description": "Handles credit card payments, tokenization, and payment method management via PayTheory",
                    "data_access": {
                        "event": "",
                        "read": [],
                        "write": []
                    }
                }
            ],
            "commands": [],
            "content": [],
            "effects": [],
            "views": []
        },
        "variables": [
            {"name": "paytheory_merchant_id", "sensitive": true},
            {"name": "paytheory_public_key"},
            {"name": "paytheory_secret_key", "sensitive": true},
            {"name": "paytheory_partner"},
            {"name": "paytheory_environment"}
        ],
        "tags": {},
        "references": [],
        "license": "",
        "readme": "./README.md"
    }
    ```
The `url_permissions` above are abbreviated — the full manifest lists every Pay Theory SDK, token-service, and tags-static URL across the production, sandbox, and lab environments.
##  handlers/ 
###  paytheory_payment_processor.py 
**Purpose and Functionality**
This file defines `PayTheoryPaymentProcessor`, a subclass of `CardPaymentProcessor`. It implements the methods Canvas calls throughout a payment workflow: rendering the tokenization forms, charging a card, and listing/adding/removing a patient's saved cards. Card data is tokenized client-side by Pay Theory's JS SDK, so the plugin never handles raw card numbers.
**Core Logic**
  - `payment_form` / `add_card_form` render the Pay Theory JS SDK form (via a Django template) for paying or saving a card.
  - `charge` sends the tokenized payment method to Pay Theory's GraphQL API and maps the result onto a `CardTransaction`, treating `PENDING`/`SETTLED`/`SUCCESS`/`SUCCEEDED` statuses as successful.
  - `payment_methods` maps a patient to a Pay Theory payor (via `canvas_patient_id` metadata) and returns their saved cards as `PaymentMethod` effects.
  - `add_payment_method` / `remove_payment_method` save and disable cards for the patient's payor.
    ```python
    from decimal import Decimal
    from typing import Any
    from canvas_sdk.effects.payment_processor import (
        AddPaymentMethodResponse,
        CardTransaction,
        PaymentMethod,
        PaymentProcessorForm,
        RemovePaymentMethodResponse,
    )
    from canvas_sdk.handlers.payment_processors.card import CardPaymentProcessor
    from canvas_sdk.templates import render_to_string
    from canvas_sdk.v1.data import Patient
    class PayTheoryPaymentProcessor(CardPaymentProcessor):
        """Custom payment processor for handling credit card payments with PayTheory."""
        def payment_form(self, patient: Patient | None = None) -> PaymentProcessorForm:
            content = render_to_string(
                "templates/form.html",
                {
                    "intent": self.PaymentIntent.PAY,
                    "public_api_key": self.public_api_key,
                    "sdk_url": self.sdk_url,
                },
            )
            return PaymentProcessorForm(content=content, intent=self.PaymentIntent.PAY)
        def charge(
            self, amount: Decimal, token: str, patient: Patient | None = None, **kwargs: Any
        ) -> CardTransaction:
            transaction = self.api.create_transaction(
                TransactionInput(payment_method_id=token, amount=amount)
            )
            status = (transaction.get("status") or "").upper()
            return CardTransaction(
                success=status in SUCCESS_STATUSES,
                transaction_id=transaction["transaction_id"],
                api_response=transaction,
            )
        def payment_methods(self, patient: Patient | None = None) -> list[PaymentMethod]:
            if not patient:
                return []
            payor_id = self.get_or_create_payor_id(patient)
            if not payor_id:
                return []
            return [self._to_payment_method(m) for m in self.api.get_payment_methods(payor_id=payor_id)]
        def add_payment_method(
            self, token: str, patient: Patient, **kwargs: Any
        ) -> AddPaymentMethodResponse:
            return AddPaymentMethodResponse(success=True)
        def remove_payment_method(self, token: str, patient: Patient) -> RemovePaymentMethodResponse:
            result = self.api.disable_payment_method(payment_method_id=token)
            return RemovePaymentMethodResponse(success=result)
    ```
The excerpt above is abridged; see the [source on GitHub](https://github.com/Medical-Software-Foundation/canvas/tree/main/extensions/paytheory-payment-processor) for the full implementation, the Pay Theory API client, and the form template.
----- END PAGE https://docs.canvasmedical.com/sdk/example-paytheory_payment_processor/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/example-portal-customization-launch_application/
> **Note:** [View the source](https://github.com/canvas-medical/canvas-plugins/tree/main/example-plugins/example_patient_portal_page) for this plugin on GitHub. 
This plugin demonstrates how to embed custom patient facing content and tools to the Canvas patient portal. It provides an example of how to expose new patient-facing content or features, such as educational materials, forms, or interactive tools. On open, the plugin can launch an application and return effects to update the portal UI or patient data as needed.
##  CANVAS_MANIFEST.json 
    ```json
    {
        "sdk_version": "0.1.4",
        "plugin_version": "0.0.1",
        "name": "example_patient_portal_page",
        "description": "Edit the description in CANVAS_MANIFEST.json",
        "url_permissions": [
            {
                "url": "https://www.canvasmedical.com/extensions",
                "permissions": ["ALLOW_SAME_ORIGIN", "SCRIPTS", "MICROPHONE", "CAMERA"]
            }
        ],
        "components": {
            "applications": [
                {
                    "class": "example_patient_portal_page.applications.my_application:MyApplication",
                    "name": "My Cool Tool",
                    "description": "Defines the menu item and what it should launch.",
                    "scope": "portal_menu_item",
                    "icon": "assets/icon.png"
                }
            ],
            "handlers": [
                {
                    "class": "example_patient_portal_page.handlers.my_web_app:MyWebApp",
                    "description": "Serves the application"
                }
            ],
            "commands": [],
            "content": [],
            "effects": [],
            "views": []
        },
        "variables": [],
        "tags": {},
        "references": [],
        "license": "",
        "diagram": false,
        "readme": "./README.md"
    }
    ```
##  handlers/ 
###  my_web_app.py 
This file defines a simple web application, MyWebApp, that serves HTML, JavaScript, and CSS content.
**Authentication**
The app uses session credentials to check if a user is logged in before providing access to its endpoints. This is done in the authenticate method, which returns True only if a logged-in user exists.
**Endpoints**
  - **/app/patient-portal-application** : 
    - Retrieves the current logged-in Patient using an ID supplied in the request headers.
    - Renders an HTML template ("static/index.html") and supplies the patient's first and last name as context variables.
    - Returns a rendered HTML page as the response.
  - **/app/main.js** : 
    - Serves the contents of "static/main.js" as JavaScript.
  - **/app/styles.css** : 
    - Serves the contents of "static/styles.css" as CSS.
**Template Rendering**
All files (HTML, JS, CSS) are rendered using the Canvas SDK's render_to_string function, allowing the inclusion of dynamic server-side data, especially for the HTML response.
**Security**
Only authenticated (logged-in) users can access any endpoint. The app enforces this globally by overriding the authenticate method.
    ```python
    from http import HTTPStatus
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.simple_api import HTMLResponse, Response
    from canvas_sdk.handlers.simple_api import SessionCredentials, SimpleAPI, api
    from canvas_sdk.templates import render_to_string
    from canvas_sdk.v1.data.patient import Patient
    #
    # Check out https://docs.canvasmedical.com/sdk/handlers-simple-api-http
    class MyWebApp(SimpleAPI):
        PREFIX = "/app"
        # Using session credentials allows us to ensure only logged in users can
        # access this.
        def authenticate(self, credentials: SessionCredentials) -> bool:
            return credentials.logged_in_user != None
        # Serve templated HTML
        @api.get("/patient-portal-application")
        def index(self) -> list[Response | Effect]:
            logged_in_user = Patient.objects.get(id=self.request.headers["canvas-logged-in-user-id"])
            context = {
                "first_name": logged_in_user.first_name,
                "last_name": logged_in_user.last_name,
            }
            return [
                HTMLResponse(
                    render_to_string("static/index.html", context),
                    status_code=HTTPStatus.OK,
                )
            ]
        # Serve the contents of a js file
        @api.get("/main.js")
        def get_main_js(self) -> list[Response | Effect]:
            return [
                Response(
                    render_to_string("static/main.js").encode(),
                    status_code=HTTPStatus.OK,
                    content_type="text/javascript",
                )
            ]
        # Serve the contents of a css file
        @api.get("/styles.css")
        def get_css(self) -> list[Response | Effect]:
            return [
                Response(
                    render_to_string("static/styles.css").encode(),
                    status_code=HTTPStatus.OK,
                    content_type="text/css",
                )
            ]
    ```
##  assets/ 
###  icon.png 
This icon is displayed in the portal menu.
##  applications/ 
###  my_application.py 
The code defines a custom application called `MyApplication`, which is the mechanism for registering the application in the portal menu..
**Key Functionality**
  - The core implementation is within the `on_open` method, which handles the application's "open" event.
  - When the application is opened, it triggers a launch effect (`LaunchModalEffect`) that opens an iframe modal.
  - The iframe modal displays the content found at the URL `/plugin-io/api/example_patient_portal_page/app/patient-portal-application`.
  - The effect specifically targets the main page area (`target=LaunchModalEffect.TargetType.PAGE`) for embedding.
  - The `on_open` method is a customization point where additional logic could be inserted—for example, dynamic URL selection based on application state or data.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.launch_modal import LaunchModalEffect
    from canvas_sdk.handlers.application import Application
    class MyApplication(Application):
        """An embeddable application that can be registered to Canvas."""
        def on_open(self) -> Effect:
            """Handle the on_open event."""
            # Implement this method to handle the application on_open event.
            # You can look up data here to be used in knowing what to launch, if
            # what you're launching depends on some dynamic criteria.
            return LaunchModalEffect(
                # This URL is what will get iframed. It can be hosted elsewhere,
                # or it could be hosted by your plugin! Canvas plugins can serve
                # html, css, js, or json.
                #
                # If embedding a remote URL, be sure to declare it in the URL
                # permissions section of your plugin's CANVAS_MANIFEST.json
                url="/plugin-io/api/example_patient_portal_page/app/patient-portal-application",
                target=LaunchModalEffect.TargetType.PAGE,
            ).apply()
    ```
##  static/ 
###  main.js 
Static javascript referenced by the html template.
###  styles.css 
Stylesheets referenced by the html template.
###  index.html 
Templated HTML to render when the menu item is clicked.
----- END PAGE https://docs.canvasmedical.com/sdk/example-portal-customization-launch_application/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/example-portal-customization-widgets/
> **Note:** [View the source](https://github.com/canvas-medical/canvas-plugins/tree/main/example-plugins/patient_portal_plugin) for this plugin on GitHub. 
##  Description 
The Patient Portal Widgets Plugin provides various widgets for the patient portal. The plugin listens to the `PATIENT_PORTAL__WIDGET_CONFIGURATION` event.
##  Widgets 
###  Header Widget 
The Header Widget displays the patient's preferred name and has quick links for messaging and scheduling.
For more, visit the [Canvas SDK documentation](https://docs.canvasmedical.com/sdk/data-patient/)
###  Care Team Widget 
It is used to display a compact widget in the Patient Portal that lists the active care team members for a patient.
It renders a scrollable list in a compact plugin format of all active members.
For more information, visit the [Canvas SDK documentation](https://docs.canvasmedical.com/sdk/data-care-team/).
###  Footer Widget 
The Footer Widget displays the support contact information for the patient.
##  Secrets 
The Patient Portal Plugin uses the following secrets:
  - `BACKGROUND_COLOR`: The background color for the widgets. Defaults to `#17634d`.
  - `EMERGENCY_CONTACT`: The emergency contact information. Defaults to `1-888-555-5555`.
In order to deal with `SECRETS` take a look at the [SDK documentation](https://docs.canvasmedical.com/sdk/secrets/).
##  CANVAS_MANIFEST.json 
    ```json
    {
        "sdk_version": "0.1.4",
        "plugin_version": "0.0.1",
        "name": "patient_portal_plugin",
        "description": "Patient Portal Plugin for Canvas",
        "components": {
            "handlers": [
                {
                    "class": "patient_portal_plugin.handlers.patient_portal_handler:PatientPortalHandler",
                    "description": "The handler that listens for the patient portal `PATIENT_PORTAL__WIDGET_CONFIGURATION` and responds with the patient portal widgets"
                }
            ],
            "commands": [],
            "content": [],
            "effects": [],
            "views": []
        },
        "variables": [
            {"name": "BACKGROUND_COLOR", "sensitive": false},
            {"name": "EMERGENCY_CONTACT", "sensitive": false}
        ],
        "tags": {},
        "references": [],
        "license": "",
        "diagram": false,
        "readme": "./README.md"
    }
    ```
##  templates/ 
###  care_team_widget.html 
###  header_widget.html 
###  footer_widget.html 
##  handlers/ 
###  patient_portal_handler.py 
**Purpose**
The code defines a handler, PatientPortalHandler, for the Canvas Medical SDK that renders custom widgets on the patient portal within Canvas. It listens for a specific widget configuration event and responds by displaying a set of widgets (header, care team, and footer) with certain configurable properties.
**Event Handling**
The handler is triggered (via RESPONDS_TO) when the event type PATIENT_PORTAL__WIDGET_CONFIGURATION occurs. When Canvas requests portal widget configuration for a patient, this handler gets called.
**Widgets Produced**
  - **Header Widget** : Collects basic patient information (first name, last name, etc.), constructs a "preferred full name," and passes it to an HTML template along with a configurable background color (using either a secret or a default).
  - **Care Team Widget** : Fetches the patient's active care team members. For each member, gathers name components, professional role, profile photo URL, and composes formatted display strings. This list is rendered in a compact widget, styled with the background color as a title color.
  - **Footer Widget** : Renders a footer using a background color and an emergency contact number, both customizable via secrets, falling back to sensible defaults if not specified.
**Widget Rendering**
For each widget, an appropriate HTML template is rendered with the relevant payload/context, and the widget is then wrapped as a PortalWidget with defined size and priority. These effects are returned as a list which the Canvas platform uses to display each widget on the patient portal.
**Customization and Defaults**
Two elements are customizable via the plugin's secrets dictionary:
  - BACKGROUND_COLOR: Sets the color for visual elements (widgets, titles).
  - EMERGENCY_CONTACT: Sets the emergency contact number in the footer.
If these are not provided, default values are used:
  - DEFAULT_BACKGROUND_COLOR = "#17634d"
  - DEFAULT_EMERGENCY_CONTACT = "1-888-555-5555"
**Summary**
This file enables a Canvas plugin to inject and style custom patient-facing widgets: a personalized header, a care team overview, and an emergency contact panel, all triggered when the platform requests configuration for the patient portal widgets. It leverages data models and template rendering of the Canvas SDK, with runtime customization via plugin secrets.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.widgets import PortalWidget
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers.base import BaseHandler
    from canvas_sdk.templates import render_to_string
    from canvas_sdk.v1.data import Patient
    from canvas_sdk.v1.data.care_team import CareTeamMembership, CareTeamMembershipStatus
    # Inherit from BaseHandler to properly get registered for events
    class PatientPortalHandler(BaseHandler):
        """Handler responsible for rendering a patient portal widgets."""
        RESPONDS_TO = EventType.Name(EventType.PATIENT_PORTAL__WIDGET_CONFIGURATION)
        # Default background and title color for the portal's widgets if not provided in secrets
        DEFAULT_BACKGROUND_COLOR = "#17634d"
        # Default emergency contact number if not provided in secrets
        DEFAULT_EMERGENCY_CONTACT = "1-888-555-5555"
        def compute(self) -> list[Effect]:
            """This method gets called when an event of the type RESPONDS_TO is fired."""
            return [
                self.header_widget,
                self.care_team_widget,
                self.footer_widget,
            ]
        @property
        def header_widget(self) -> Effect:
            """Constructs the header widget for the patient portal."""
            # Get the patient needed fields to generate the preferred full name
            patient = Patient.objects.only("first_name", "last_name", "suffix", "nickname").get(id=self.target)
            payload = {
                "preferred_full_name": patient.preferred_full_name,
                "background_color": self.background_color,
            }
            header_widget = PortalWidget(
                content=render_to_string("templates/header_widget.html", payload),
                size=PortalWidget.Size.EXPANDED,
                priority=10,
            )
            return header_widget.apply()
        @property
        def care_team_widget(self) -> Effect:
            """Constructs the care team widget for the patient portal."""
            patient_care_team = CareTeamMembership.objects.values(
                "staff__first_name",
                "staff__last_name",
                "staff__prefix",
                "staff__suffix",
                "staff__photos__url",
                "role_display",
            ).filter(
                patient__id=self.target,
                status=CareTeamMembershipStatus.ACTIVE,
            )
            care_team = []
            for member in patient_care_team:
                # Aliasing the member's name components for clarity
                name = f"{member['staff__first_name']} {member['staff__last_name']}"
                prefixed_name = f"{member['staff__prefix']} " if member['staff__prefix'] else name
                professional_name = f"{prefixed_name}, {member['staff__suffix']}" if member['staff__suffix'] else prefixed_name
                photo_url = member['staff__photos__url']
                role = member['role_display']
                care_team.append(
                    {
                        "name": name,
                        "prefixed_name": prefixed_name,
                        "professional_name": professional_name,
                        "photo_url": photo_url,
                        "role": role,
                    }
                )
            payload = {
                "care_team": care_team,
                "title_color": self.background_color,
            }
            care_team_widget = PortalWidget(
                content=render_to_string("templates/care_team_widget.html", payload),
                size=PortalWidget.Size.COMPACT,
                priority=11,
            )
            return care_team_widget.apply()
        @property
        def footer_widget(self) -> Effect:
            """This method gets called when an event of the type RESPONDS_TO is fired."""
            return PortalWidget(
                content=render_to_string("templates/footer_widget.html", {
                    "background_color": self.background_color,
                    "emergency_contact": self.emergency_contact,
                }),
                size=PortalWidget.Size.EXPANDED,
                priority=12,
            ).apply()
        @property
        def background_color(self) -> str:
            """Get the background color from secrets, defaulting to a specific color if not set."""
            return self.secrets.get("BACKGROUND_COLOR") or self.DEFAULT_BACKGROUND_COLOR
        @property
        def emergency_contact(self) -> str:
            """Get the emergency contact from secrets, defaulting to a specific contact if not set."""
            return self.secrets.get("EMERGENCY_CONTACT") or self.DEFAULT_EMERGENCY_CONTACT
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/example-portal-customization-widgets/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/example-send_all_prescriptions/
> **Note:** [View the source](https://github.com/canvas-medical/canvas-plugins/tree/main/example-plugins/send_all_prescriptions) for this plugin on GitHub. 
A Canvas plugin that adds a "Send Prescriptions" button to note footers, allowing healthcare providers to send all committed prescriptions in a note with a single click.
##  SDK Features 
  - Creates an [ActionButton](/sdk/handlers-action-buttons/) that uses the [Note](/sdk/data-note/) context to get all committed prescribe [Commands](/sdk/data-command/) in a Note
  - Returns a list of [PrescribeCommand](sdk/commands/#prescribe) effects with [`send`](/sdk/commands/#send) action request
##  Structure 
    ```plaintext
    send_all_prescriptions/
    ├── CANVAS_MANIFEST.json          # Plugin configuration
    ├── README.md                     # Documentation
    ├── handlers/
    │   └── handler.py                # Defines SendPrescriptionButtonHandler class
    ```
##  CANVAS_MANIFEST.json 
    ```json
    {
        "sdk_version": "0.1.4",
        "plugin_version": "1.0.0",
        "name": "send_all_prescriptions",
        "description": "Adds a 'Send Prescriptions' button to note footers that allows providers to send all committed prescriptions in a note with a single click, streamlining the prescription workflow.",
        "components": {
            "handlers": [
                {
                    "class": "send_all_prescriptions.handlers.handler:SendPrescriptionButtonHandler",
                    "description": "Action button that sends all committed prescriptions in the current note.",
                    "data_access": {
                        "event": "",
                        "read": [],
                        "write": []
                    }
                }
            ],
            "commands": [],
            "content": [],
            "effects": [],
            "views": []
        },
        "variables": [],
        "tags": {},
        "references": ["https://docs.canvasmedical.com/sdk/handlers-action-buttons/", "https://docs.canvasmedical.com/sdk/commands/#prescribe"],
        "license": "",
        "diagram": false,
        "readme": "./README.md"
    }
    ```
##  handlers/ 
###  handler.py 
This file defines a custom button handler. It provides functionality to send all prescription commands associated with a specific note when the button is activated.
  - The main class, `SendPrescriptionButtonHandler`, inherits from `ActionButton` and represents a custom action button shown in the UI (specifically, at the note footer).
  - The button is labeled "Send Prescriptions" and assigned a unique key "SEND_ALL_PRESCRIPTIONS".
**Button Handling Logic**
  - The `handle` method is the core logic, executed when the user clicks the button.
  - It obtains the `note_id` from the plugin execution context (self.context).
  - It queries the database for all commands of type `"prescribe"` that are committed (i.e., have an associated committer) and belong to the given note.
  - For each qualifying command, it creates a `PrescribeCommand`, sets its UUID to match the command, and calls `send()`, which returns an `Effect`.
  - All created effects are collected into a list and returned; these effects trigger the actual process of sending prescriptions.
    ```python
    from canvas_sdk.commands import PrescribeCommand
    from canvas_sdk.effects import Effect
    from canvas_sdk.handlers.action_button import ActionButton
    from canvas_sdk.v1.data import Command
    class SendPrescriptionButtonHandler(ActionButton):
        BUTTON_TITLE = "Send Prescriptions"
        BUTTON_KEY = "SEND_ALL_PRESCRIPTIONS"
        BUTTON_LOCATION = ActionButton.ButtonLocation.NOTE_FOOTER
        def handle(self) -> list[Effect]:
            note_id = self.context.get("note_id")
            effects = []
            # get all committed prescribe commands
            prescribe_commands = Command.objects.filter(note_id=note_id, schema_key="prescribe", committer__isnull=False)
            for command in prescribe_commands:
                prescribe = PrescribeCommand()
                prescribe.command_uuid = str(command.id)
                effects.append(prescribe.send())
            return effects
    ```
##  Customization 
###  Button Appearance 
You can customize the button by modifying the class attributes:
    ```python
    BUTTON_TITLE = "Your Custom Title"    # Change button text
    BUTTON_KEY = "YOUR_UNIQUE_KEY"        # Change button identifier
    ```
###  Button Location 
Change where the button appears by modifying:
    ```python
    BUTTON_LOCATION = ActionButton.ButtonLocation.NOTE_FOOTER  # or other locations
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/example-send_all_prescriptions/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/example-sendgrid_email/
> **Note:** [View the source](https://github.com/canvas-medical/canvas-plugins/tree/main/example-plugins/sendgrid_email) for this plugin on GitHub. 
##  Description 
Plugin that provides a SimpleAPI for sending emails, querying sent emails, managing inbound and outbound webhooks, and handling email status callbacks using the Canvas SDK's SendGrid client. It supports inline images, file attachments, inbound email parsing, and outbound event tracking. Includes a chart application that renders a form interface for email management directly from the chart.
##  Configuration 
This example plugin defines the following "secrets" in the manifest file:
    ```plaintext
        "variables": [
            {"name": "SendgridAPIKey", "sensitive": true}
        ],
    ```
Once defined in the `MANIFEST.json`, set the secrets for your plugin in the Admin UI of your Canvas EMR. [Read more](https://docs.canvasmedical.com/sdk/secrets/)
###  SendgridAPIKey 
Your [SendGrid API key](https://docs.sendgrid.com/ui/account-and-settings/api-keys).
##  CANVAS_MANIFEST.json 
    ```json
    {
      "sdk_version": "0.85.0",
      "plugin_version": "0.0.1",
      "name": "email_sender",
      "description": "use Sendgrid to send emails, retrieve sent emails, manage inbound and outbound webhooks",
      "components": {
        "handlers": [
          {
            "class": "email_sender.handlers.email_manip:EmailManip",
            "description": "Emails with Sendgrid"
          }
        ],
        "applications": [
          {
            "class": "email_sender.handlers.email_form_app:EmailFormApp",
            "name": "Emails Sendgrid",
            "description": "Emails with Sendgrid",
            "icon": "static/email_sender.png",
            "scope": "patient_specific",
            "show_in_panel": false
          }
        ],
        "commands": [],
        "content": [],
        "effects": [],
        "views": []
      },
      "variables": [
        {"name": "SendgridAPIKey", "sensitive": true}
      ],
      "tags": {},
      "references": [],
      "license": "",
      "diagram": false,
      "readme": "./README.md"
    }
    ```
##  handlers/ 
###  email_manip.py 
**Purpose**
This code defines a SimpleAPI handler that exposes REST endpoints for managing email operations via the Canvas SDK's SendGrid client.
**Class Overview**
  - The main class, `EmailManip`, extends `SimpleAPI`.
  - It creates a SendGrid `EmailClient` using an API key stored in plugin secrets.
  - It uses the plugin cache system for storing webhook callback data.
**Main Workflow**
  - `POST /send_email` — Sends an email with optional inline images and file attachments.
  - `POST /emails_sent` — Queries sent emails with optional filters for recipient and date.
  - `GET /email_events/<message_id>` — Retrieves email events for a specific message.
  - `POST /inbound_webhook` — Enables/disables the SendGrid inbound parse webhook.
  - `GET /inbound_webhook` — Gets the current inbound webhook configuration status.
  - `POST /outbound_webhook` — Enables/disables the SendGrid outbound event webhook.
  - `GET /outbound_webhook` — Gets the current outbound webhook configuration status.
  - `POST /inbound_email` — Receives and caches parsed inbound emails from SendGrid.
  - `GET /inbound_email` — Retrieves the most recent inbound email from cache.
  - `POST /outbound_email_status` — Receives and caches outbound email status events.
  - `GET /outbound_email_status` — Retrieves the most recent outbound status events from cache.
**SendGrid Client Integration**
  - The `_sendgrid_client` method creates an `EmailClient` instance from `canvas_sdk.clients.sendgrid.libraries`.
  - Email composition uses structured types: `Address`, `Recipient`, `BodyContent`, `Attachment`, and `Email`.
  - Error handling uses the `RequestFailed` exception from the SendGrid client structures.
    ```python
    from datetime import UTC, datetime, timedelta
    from http import HTTPStatus
    from email_sender.constants.constants import Constants
    from canvas_sdk.caching.plugins import get_cache
    from canvas_sdk.clients.sendgrid.constants import (
        CriterionOperation,
        RecipientType,
    )
    from canvas_sdk.clients.sendgrid.libraries import EmailClient
    from canvas_sdk.clients.sendgrid.structures import (
        Address,
        Attachment,
        BodyContent,
        CriterionDatetime,
        Email,
        EmailEvent,
        EventWebhook,
        LoggedEmailCriteria,
        ParsedEmail,
        ParseSetting,
        Recipient,
        RequestFailed,
        Settings,
    )
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.simple_api import JSONResponse, Response
    from canvas_sdk.handlers.simple_api import Credentials, SimpleAPI, api
    from logger import log
    class EmailManip(SimpleAPI):
        """API handler for SendGrid email operations including sending, webhooks, and logging."""
        PREFIX = None
        def authenticate(self, credentials: Credentials) -> bool:
            """Authenticate the API request. Always returns True (no authentication required)."""
            return True
        def _sendgrid_client(self) -> EmailClient:
            """Create and return a configured SendGrid email client."""
            settings = Settings(key=self.secrets[Constants.sendgrid_api_key])
            return EmailClient(settings)
        @api.get("/email_events/<message_id>")
        def email_events(self) -> list[Response | Effect]:
            """Retrieve email events for a specific message ID from SendGrid."""
            message_id = self.request.path_params["message_id"]
            client = self._sendgrid_client()
            try:
                result = [
                    JSONResponse(
                        client.logged_email(message_id).to_dict(),
                        status_code=HTTPStatus(HTTPStatus.OK),
                    )
                ]
            except RequestFailed as e:
                result = [
                    JSONResponse({"information": e.message}, status_code=HTTPStatus(e.status_code))
                ]
            return result
        @api.post("/emails_sent")
        def emails_sent(self) -> list[Response | Effect]:
            """Query sent emails from SendGrid with optional filters for recipient and date."""
            content = self.request.json()
            to_email = content.get("emailTo")
            on_day = content.get("onDay")
            max_logs = content.get("maxLogs")
            client = self._sendgrid_client()
            try:
                message_created_at = []
                if on_day:
                    date_time = datetime.strptime(on_day, "%Y-%m-%d")
                    message_created_at = [
                        CriterionDatetime(
                            date_time=date_time,
                            operation=CriterionOperation.GREATER_THAN_OR_EQUAL,
                        ),
                    ]
                    next_date = date_time + timedelta(days=1)
                    if next_date < datetime.now():
                        message_created_at.append(
                            CriterionDatetime(
                                date_time=next_date,
                                operation=CriterionOperation.LOWER_THAN_OR_EQUAL,
                            )
                        )
                criteria = LoggedEmailCriteria(
                    message_id="",
                    subject="",
                    to_email=to_email,
                    reason="",
                    status=[],
                    message_created_at=message_created_at,
                )
                result = [
                    JSONResponse(
                        [email.to_dict() for email in client.logged_emails(criteria, max_logs)],
                        status_code=HTTPStatus(HTTPStatus.OK),
                    )
                ]
            except RequestFailed as e:
                result = [
                    JSONResponse({"information": e.message}, status_code=HTTPStatus(e.status_code))
                ]
            return result
        @api.post("/send_email")
        def send_email(self) -> list[Response | Effect]:
            """Send an email via SendGrid with optional inline images and attachments."""
            content = self.request.json()
            email_from = content.get("emailFrom")
            email_to = content.get("emailTo")
            email_cc = content.get("emailCc")
            subject = content.get("subject")
            body = content.get("body")
            inline_url = content.get("inlineUrl")
            attachment_url = content.get("attachmentUrl")
            client = self._sendgrid_client()
            try:
                sender = Address(email=email_from, name="Sender")
                reply_tos = [Address(email=email_from, name="ReplyTo")]
                recipients = [
                    Recipient(address=Address(email=email_to, name="RecTo"), type=RecipientType.TO)
                ]
                if email_cc:
                    cc = Recipient(address=Address(email=email_cc, name="RecCc"), type=RecipientType.CC)
                    recipients.append(cc)
                subject = f"{subject} - {datetime.now(UTC).strftime('%H:%M:%S')}"
                bodies = [BodyContent(type="text/plain", value=body)]
                attachments = []
                if inline_url:
                    attached = Attachment.from_url_inline(
                        inline_url, {}, "inline_picture.png", "pictureId"
                    )
                    attachments.append(attached)
                    html_body = BodyContent(
                        type="text/html",
                        value=f"<html><body>{body}<br/>"
                        '<img src="cid:pictureId" width="200px"/><br/>'
                        "Bye!</body></html>",
                    )
                    bodies.append(html_body)
                if attachment_url:
                    attached = Attachment.from_url(attachment_url, {}, "attached_picture.png")
                    attachments.append(attached)
                email = Email(
                    sender=sender,
                    reply_tos=reply_tos,
                    recipients=recipients,
                    subject=subject,
                    bodies=bodies,
                    attachments=attachments,
                    send_at=Email.now(),
                )
                result = [
                    JSONResponse(
                        {"successful": client.simple_send(email)},
                        status_code=HTTPStatus(HTTPStatus.OK),
                    )
                ]
            except RequestFailed as e:
                result = [
                    JSONResponse({"information": e.message}, status_code=HTTPStatus(e.status_code))
                ]
            return result
        def parser_url(self) -> str:
            """Build the URL for the inbound email parser webhook endpoint."""
            host = f"https://{self.environment[Constants.customer_identifier]}.canvasmedical.com"
            return f"{host}{Constants.plugin_api_base_route}/inbound_email"
        @api.post("/inbound_webhook")
        def inbound_webhook_toggle(self) -> list[Response | Effect]:
            """Enable or disable the SendGrid inbound parse webhook."""
            content = self.request.json()
            enabled = content.get("enabled")
            hostname = content.get("hostname")
            client = self._sendgrid_client()
            try:
                result = [JSONResponse({"enabled": enabled}, status_code=HTTPStatus(HTTPStatus.OK))]
                parser_url = self.parser_url()
                parsers = [
                    parser.hostname
                    for parser in client.parser_setting_list()
                    if parser.url == parser_url
                ]
                if enabled and not parsers:
                    setting = ParseSetting(
                        url=self.parser_url(),
                        hostname=hostname,
                        spam_check=True,
                        send_raw=False,
                    )
                    client.parser_setting_add(setting)
                if not enabled and parsers:
                    client.parser_setting_delete(parsers[0])
            except RequestFailed as e:
                result = [
                    JSONResponse({"information": e.message}, status_code=HTTPStatus(e.status_code))
                ]
            return result
        @api.get("/inbound_webhook")
        def inbound_webhook_get(self) -> list[Response | Effect]:
            """Get the current status of the inbound parse webhook configuration."""
            client = self._sendgrid_client()
            try:
                parser_url = self.parser_url()
                parsers = [
                    parser for parser in client.parser_setting_list() if parser.url == parser_url
                ]
                response = {"enabled": bool(parsers), "hostname": ""}
                if parsers:
                    response["hostname"] = parsers[0].hostname
                result = [JSONResponse(response, status_code=HTTPStatus(HTTPStatus.OK))]
            except RequestFailed as e:
                result = [
                    JSONResponse({"information": e.message}, status_code=HTTPStatus(e.status_code))
                ]
            return result
        def webhook_url(self) -> str:
            """Build the URL for the outbound email status webhook endpoint."""
            host = f"https://{self.environment[Constants.customer_identifier]}.canvasmedical.com"
            return f"{host}{Constants.plugin_api_base_route}/outbound_email_status"
        @api.post("/outbound_webhook")
        def outbound_webhook_toggle(self) -> list[Response | Effect]:
            """Enable or disable the SendGrid outbound event webhook."""
            content = self.request.json()
            enabled = content.get("enabled")
            client = self._sendgrid_client()
            try:
                result = [JSONResponse({"enabled": enabled}, status_code=HTTPStatus(HTTPStatus.OK))]
                webhook_url = self.webhook_url()
                webhook_ids = [
                    webhook.id for webhook in client.event_webhook_list() if webhook.url == webhook_url
                ]
                if enabled and not webhook_ids:
                    event = EventWebhook(
                        url=webhook_url,
                        enabled=True,
                        group_resubscribe=False,
                        group_unsubscribe=False,
                        delivered=True,
                        spam_report=True,
                        bounce=True,
                        unsubscribe=False,
                        processed=True,
                        open=True,
                        click=True,
                        dropped=True,
                        friendly_name="Canvas Plugin Webhook",
                    )
                    client.event_webhook_add(event)
                if not enabled and webhook_ids:
                    client.event_webhook_delete(webhook_ids[0])
            except RequestFailed as e:
                result = [
                    JSONResponse({"information": e.message}, status_code=HTTPStatus(e.status_code))
                ]
            return result
        @api.get("/outbound_webhook")
        def outbound_webhook_get(self) -> list[Response | Effect]:
            """Get the current status of the outbound event webhook configuration."""
            client = self._sendgrid_client()
            try:
                webhook_url = self.webhook_url()
                enabled = any(
                    webhook.id for webhook in client.event_webhook_list() if webhook.url == webhook_url
                )
                result = [JSONResponse({"enabled": enabled}, status_code=HTTPStatus(HTTPStatus.OK))]
            except RequestFailed as e:
                result = [
                    JSONResponse({"information": e.message}, status_code=HTTPStatus(e.status_code))
                ]
            return result
        @api.get("/outbound_email_status")
        def last_outbound_status(self) -> list[Response | Effect]:
            """Retrieve the most recent outbound email status events from cache."""
            return [
                JSONResponse(
                    self.cache_retrieve("outbound_email_status"), status_code=HTTPStatus(HTTPStatus.OK)
                )
            ]
        @api.post("/outbound_email_status")
        def outbound_status_save(self) -> list[Response | Effect]:
            """Receive and cache outbound email status events from SendGrid webhook."""
            events = [EmailEvent.from_dict(item) for item in self.request.json()]
            self.cache_save("outbound_email_status", [e.to_dict() for e in events])
            log.info(f"outbound status received:{len(events)}")
            return [Response(status_code=HTTPStatus(HTTPStatus.OK))]
        @api.get("/inbound_email")
        def last_inbound_email(self) -> list[Response | Effect]:
            """Retrieve the most recent inbound email from cache."""
            return [
                JSONResponse(
                    self.cache_retrieve("inbound_treatment"),
                    status_code=HTTPStatus(HTTPStatus.OK),
                )
            ]
        @api.post("/inbound_email")
        def inbound_email_save(self) -> list[Response | Effect]:
            """Receive and cache parsed inbound emails from SendGrid webhook."""
            form = self.request.form_data()
            message = {}
            files = []
            for key, value in form.multi_items():
                if (
                    hasattr(value, "file")
                    and hasattr(value, "filename")
                    and hasattr(value, "content_type")
                ):
                    files.append((key, value))
                else:
                    if hasattr(value, "value"):
                        message[key] = value.value
                    elif isinstance(value, str):
                        message[key] = value
                    else:
                        message[key] = str(value)
            parsed = ParsedEmail.from_dict(message)
            self.cache_save("inbound_treatment", [parsed.to_dict()])
            log.info(f"inbound email received from {parsed.email_from} to {parsed.email_to}")
            return [Response(status_code=HTTPStatus(HTTPStatus.OK))]
        @classmethod
        def cache_save(cls, key: str, payload: list) -> None:
            """Store a payload in the plugin cache under the given key."""
            get_cache().set(key, payload)
        @classmethod
        def cache_retrieve(cls, key: str) -> list:
            """Retrieve a cached payload by key, returning an empty list if not found."""
            return get_cache().get(key) or []
    ```
###  email_form_app.py 
**Purpose**
This code defines an Application handler that launches a modal form in the right chart pane for interacting with the SendGrid email API endpoints.
    ```python
    from email_sender.constants.constants import Constants
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.launch_modal import LaunchModalEffect
    from canvas_sdk.handlers.application import Application
    from canvas_sdk.templates import render_to_string
    class EmailFormApp(Application):
        """Application handler that displays the email sender form in a modal."""
        def on_open(self) -> Effect:
            """Render and launch the email form modal in the right chart pane."""
            content = render_to_string(
                "templates/email_form.html",
                {
                    "sendEmailURL": f"{Constants.plugin_api_base_route}/send_email",
                    "emailsSentURL": f"{Constants.plugin_api_base_route}/emails_sent",
                    "emailEventsURL": f"{Constants.plugin_api_base_route}/email_events",
                    "outboundWebhookURL": f"{Constants.plugin_api_base_route}/outbound_webhook",
                    "outboundStatusesURL": f"{Constants.plugin_api_base_route}/outbound_email_status",
                    "inboundWebhookURL": f"{Constants.plugin_api_base_route}/inbound_webhook",
                    "inboundEmailURL": f"{Constants.plugin_api_base_route}/inbound_email",
                },
            )
            return LaunchModalEffect(
                content=content,
                target=LaunchModalEffect.TargetType.RIGHT_CHART_PANE,
            ).apply()
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/example-sendgrid_email/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/example-twilio_sms_mms/
> **Note:** [View the source](https://github.com/canvas-medical/canvas-plugins/tree/main/example-plugins/twilio_sms_mms) for this plugin on GitHub. 
##  Description 
Plugin that provides a SimpleAPI for sending and receiving SMS/MMS messages using the Canvas SDK's Twilio client. It supports listing phone numbers, sending messages, retrieving message history and media, managing inbound webhooks, handling delivery status callbacks, and auto-replying to inbound messages. Includes a chart application that renders a form interface for SMS management directly from the chart.
##  Configuration 
This example plugin defines the following "secrets" in the manifest file:
    ```plaintext
        "variables": [
            {"name": "TwilioAccountSID", "sensitive": true},
            {"name": "TwilioAPIKey", "sensitive": true},
            {"name": "TwilioAPISecret", "sensitive": true}
        ],
    ```
Once defined in the `MANIFEST.json`, set the secrets for your plugin in the Admin UI of your Canvas EMR. [Read more](https://docs.canvasmedical.com/sdk/secrets/)
###  TwilioAccountSID 
Your [Twilio Account SID](https://www.twilio.com/docs/iam/api/account).
###  TwilioAPIKey 
Your [Twilio API Key SID](https://www.twilio.com/docs/iam/api-keys).
###  TwilioAPISecret 
Your [Twilio API Key Secret](https://www.twilio.com/docs/iam/api-keys).
##  CANVAS_MANIFEST.json 
    ```json
    {
      "sdk_version": "0.85.0",
      "plugin_version": "0.0.1",
      "name": "twilio_sms_mms",
      "description": "use Twillio to send and receive SMS/MMS",
      "components": {
        "handlers": [
          {
            "class": "twilio_sms_mms.handlers.sms_manip:SmsManip",
            "description": "SMS/MMS with Twilio"
          }
        ],
        "applications": [
          {
            "class": "twilio_sms_mms.handlers.sms_form_app:SmsFormApp",
            "name": "SMS Twilio",
            "description": "SMS/MMS with Twilio",
            "icon": "static/twilio_sms_mms.png",
            "scope": "patient_specific",
            "show_in_panel": false
          }
        ],
        "commands": [],
        "content": [],
        "effects": [],
        "views": []
      },
      "variables": [
        {"name": "TwilioAccountSID", "sensitive": true},
        {"name": "TwilioAPIKey", "sensitive": true},
        {"name": "TwilioAPISecret", "sensitive": true}
      ],
      "tags": {},
      "references": [],
      "license": "",
      "diagram": false,
      "readme": "./README.md"
    }
    ```
##  handlers/ 
###  sms_manip.py 
**Purpose**
This code defines a SimpleAPI handler that exposes REST endpoints for managing SMS/MMS operations via the Canvas SDK's Twilio client.
**Class Overview**
  - The main class, `SmsManip`, extends `StaffSessionAuthMixin` and `SimpleAPI`.
  - It creates a Twilio `SmsClient` using credentials stored in plugin secrets (Account SID, API Key, API Secret).
**Main Workflow**
  - `GET /phone_list` — Retrieves all phone numbers associated with the Twilio account, including capabilities and webhook configuration.
  - `GET /message_list/<number>/<direction>` — Lists messages for a specific phone number filtered by direction (from/to).
  - `GET /message/<message_sid>` — Retrieves details of a specific message.
  - `GET /medias/<message_sid>` — Retrieves all media attachments for a specific message.
  - `DELETE /message_delete/<message_sid>` — Deletes a specific message.
  - `POST /sms_send` — Sends an SMS message with optional status callback URL.
  - `POST /inbound_webhook/<phone_sid>` — Configures the inbound webhook URL for a Twilio phone number.
  - `POST /outbound_api_status` — Handles status callbacks for outbound SMS messages.
  - `POST /inbound_treatment` — Processes incoming messages and sends automatic replies with optional media.
**Twilio Client Integration**
  - The `_twillio_client` method creates an `SmsClient` instance from `canvas_sdk.clients.twilio.libraries`.
  - Error handling uses the `RequestFailed` exception from the Twilio client structures.
  - Inbound message handling generates TwiML responses for automatic replies.
    ```python
    from http import HTTPStatus
    from canvas_sdk.clients.twilio.constants import DateOperation, HttpMethod
    from canvas_sdk.clients.twilio.libraries import SmsClient
    from canvas_sdk.clients.twilio.structures import (
        RequestFailed,
        Settings,
        SmsMms,
        StatusInbound,
        StatusOutboundApi,
    )
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.simple_api import HTMLResponse, JSONResponse, Response
    from canvas_sdk.handlers.simple_api import SimpleAPI, StaffSessionAuthMixin, api
    from logger import log
    from ..constants.constants import Constants
    class SmsManip(StaffSessionAuthMixin, SimpleAPI):
        """API handler for Twilio SMS/MMS operations."""
        PREFIX = None
        def _twillio_client(self) -> SmsClient:
            """Create and configure a Twilio SMS client."""
            settings = Settings(
                account_sid=self.secrets[Constants.twillio_account_sid],
                key=self.secrets[Constants.twillio_api_key],
                secret=self.secrets[Constants.twillio_api_secret],
            )
            return SmsClient(settings)
        @api.get("/phone_list")
        def phone_list(self) -> list[Response | Effect]:
            """Retrieve the list of phone numbers associated with the Twilio account."""
            client = self._twillio_client()
            try:
                result = [
                    JSONResponse(
                        [
                            {
                                "sid": p.sid,
                                "phoneNumber": p.phone_number,
                                "label": p.friendly_name,
                                "capabilities": p.capabilities.to_dict(),
                                "statusCallback": p.status_callback,
                                "status": p.status,
                                "inboundWebhook": {
                                    "url": p.sms_url,
                                    "method": p.sms_method.value,
                                },
                            }
                            for p in client.account_phone_numbers()
                        ],
                        status_code=HTTPStatus(HTTPStatus.OK),
                    ),
                ]
            except RequestFailed as e:
                result = [
                    JSONResponse({"information": e.message}, status_code=HTTPStatus(e.status_code))
                ]
            return result
        @api.get("/message_list/<number>/<direction>")
        def message_list(self) -> list[Response | Effect]:
            """Retrieve a list of messages for a specific phone number."""
            number = self.request.path_params["number"]
            direction = self.request.path_params["direction"]
            number_from = number if direction == "from" else ""
            number_to = number if direction == "to" else ""
            client = self._twillio_client()
            try:
                result = [
                    JSONResponse(
                        [
                            {
                                "sid": p.sid,
                                "sent": p.date_sent.isoformat(),
                                "status": p.status.value,
                                "mediaCount": p.count_media,
                            }
                            for p in client.retrieve_all_sms(
                                number_to, number_from, "", DateOperation.ON_EXACTLY
                            )
                        ],
                        status_code=HTTPStatus(HTTPStatus.OK),
                    ),
                ]
            except RequestFailed as e:
                result = [
                    JSONResponse({"information": e.message}, status_code=HTTPStatus(e.status_code))
                ]
            return result
        @api.get("/message/<message_sid>")
        def message(self) -> list[Response | Effect]:
            """Retrieve details of a specific message."""
            message_sid = self.request.path_params["message_sid"]
            client = self._twillio_client()
            try:
                result = [
                    JSONResponse(
                        client.retrieve_sms(message_sid).to_dict(),
                        status_code=HTTPStatus(HTTPStatus.OK),
                    ),
                ]
            except RequestFailed as e:
                result = [
                    JSONResponse({"information": e.message}, status_code=HTTPStatus(e.status_code))
                ]
            return result
        @api.get("/medias/<message_sid>")
        def media_list(self) -> list[Response | Effect]:
            """Retrieve all media attachments for a specific message."""
            message_sid = self.request.path_params["message_sid"]
            client = self._twillio_client()
            try:
                result = [
                    Response(
                        content_type=p.content_type,
                        content=client.retrieve_raw_media(message_sid, p.sid),
                    )
                    for p in client.retrieve_media_list(message_sid)
                ]
            except RequestFailed as e:
                result = [
                    JSONResponse({"information": e.message}, status_code=HTTPStatus(e.status_code))
                ]
            return result
        @api.delete("/message_delete/<message_sid>")
        def message_delete(self) -> list[Response | Effect]:
            """Delete a specific message from Twilio."""
            message_sid = self.request.path_params["message_sid"]
            client = self._twillio_client()
            try:
                result = [
                    JSONResponse(
                        {
                            "sid": message_sid,
                            "deleted": client.delete_sms(message_sid),
                        },
                        status_code=HTTPStatus(HTTPStatus.OK),
                    )
                ]
            except RequestFailed as e:
                result = [
                    JSONResponse({"information": e.message}, status_code=HTTPStatus(e.status_code))
                ]
            return result
        @api.post("/inbound_webhook/<phone_sid>")
        def inbound_webhook(self) -> list[Response | Effect]:
            """Configure the inbound webhook URL for a Twilio phone number."""
            phone_sid = self.request.path_params["phone_sid"]
            content = self.request.json()
            webhook_url = content["url"]
            method = HttpMethod(content["method"])
            client = self._twillio_client()
            try:
                response = client.set_inbound_webhook(phone_sid, webhook_url, method)
                result = [JSONResponse({"result": response}, status_code=HTTPStatus(HTTPStatus.OK))]
            except RequestFailed as e:
                result = [
                    JSONResponse({"information": e.message}, status_code=HTTPStatus(e.status_code))
                ]
            return result
        @api.post("/sms_send")
        def sms_send(self) -> list[Response | Effect]:
            """Send an SMS message via Twilio."""
            content = self.request.json()
            number_from = content.get("numberFrom")
            number_from_id = content.get("numberFromSid")
            number_to = content.get("numberTo")
            callback_url = content.get("callbackUrl")
            text = content.get("text")
            client = self._twillio_client()
            try:
                sms_mms = SmsMms(
                    number_from=number_from,
                    number_from_sid=number_from_id,
                    number_to=number_to,
                    message=text,
                    media_url="",
                    status_callback_url=callback_url,
                )
                response = client.send_sms_mms(sms_mms)
                result = [JSONResponse(response.to_dict(), status_code=HTTPStatus(HTTPStatus.OK))]
            except RequestFailed as e:
                result = [
                    JSONResponse({"information": e.message}, status_code=HTTPStatus(e.status_code))
                ]
            return result
        @api.post("/outbound_api_status")
        def outbound_api_status(self) -> list[Response | Effect]:
            """Handle status callbacks for outbound SMS messages."""
            status = StatusOutboundApi.callback_outbound_api(self.request.text())
            log.info(f"sms_extern: sid/status: {status.sms_sid}/{status.sms_status}")
            return [Response(status_code=HTTPStatus(HTTPStatus.OK))]
        @api.post("/inbound_treatment")
        def inbound_treatment(self) -> list[Response | Effect]:
            """Handle inbound SMS messages with automatic replies."""
            inbound = StatusInbound.callback_inbound(self.request.text())
            if "hello" in inbound.body.lower():
                reply = "Hello!"
                image = "<Media>https://img.freepik.com/free-psd/hand-drawn-summer-frame-illustration_23-2151631028.jpg</Media>"
            else:
                reply = "Say hello!"
                image = ""
            response = (
                '<?xml version="1.0" encoding="UTF-8"?>\n'
                "<Response>"
                f"<Message><Body>{reply}</Body>{image}</Message>"
                "</Response>"
            )
            return [
                HTMLResponse(
                    content=response,
                    status_code=HTTPStatus(HTTPStatus.OK),
                )
            ]
    ```
###  sms_form_app.py 
**Purpose**
This code defines an Application handler that launches a modal form in the right chart pane for interacting with the Twilio SMS/MMS API endpoints.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.launch_modal import LaunchModalEffect
    from canvas_sdk.handlers.application import Application
    from canvas_sdk.templates import render_to_string
    from ..constants.constants import Constants
    class SmsFormApp(Application):
        """Application handler for the SMS/MMS form interface."""
        def on_open(self) -> Effect:
            """Render and launch the SMS form modal in the right chart pane."""
            host = f"https://{self.environment[Constants.customer_identifier]}.canvasmedical.com"
            content = render_to_string(
                "templates/sms_form.html",
                {
                    "smsSendURL": f"{Constants.plugin_api_base_route}/sms_send",
                    "phoneListURL": f"{Constants.plugin_api_base_route}/phone_list",
                    "setInboundWebhookURL": f"{Constants.plugin_api_base_route}/inbound_webhook",
                    "messageListURL": f"{Constants.plugin_api_base_route}/message_list",
                    "messageURL": f"{Constants.plugin_api_base_route}/message",
                    "mediaURL": f"{Constants.plugin_api_base_route}/medias",
                    "deleteMessageURL": f"{Constants.plugin_api_base_route}/message_delete",
                    "defaultCallbackURL": f"{host}{Constants.plugin_api_base_route}/outbound_api_status",
                },
            )
            return LaunchModalEffect(
                content=content,
                target=LaunchModalEffect.TargetType.RIGHT_CHART_PANE,
            ).apply()
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/example-twilio_sms_mms/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/example-upsert_patient_metadata/
> **Note:** [View the source](https://github.com/canvas-medical/canvas-plugins/tree/main/example-plugins/upsert_patient_metadata) for this plugin on GitHub. 
This plugin showcases how to store patient metadata key/value pairs from a plugin using the Canvas SDK.
In this example, we extract key-value pairs from a plan command's narrative and store them as patient metadata.
##  CANVAS_MANIFEST.json 
    ```json
    {
        "sdk_version": "0.1.4",
        "plugin_version": "0.0.1",
        "name": "upsert_patient_metadata",
        "description": "Edit the description in CANVAS_MANIFEST.json",
        "components": {
            "handlers": [
                {
                    "class": "upsert_patient_metadata.handlers.my_handler:MyHandler",
                    "description": "A handler that does xyz..."
                }
            ],
            "commands": [],
            "content": [],
            "effects": [],
            "views": []
        },
        "variables": [],
        "tags": {},
        "references": [],
        "license": "",
        "diagram": false,
        "readme": "./README.md"
    }
    ```
##  handlers/ 
###  my_handler.py 
This file defines a Canvas plugin handler that uses the update of a plan command as an excuse to trigger. When a user updates a plan command and includes certain key-value data in the narrative, this handler extracts that information and saves it as patient metadata.
**Narrative Parsing**
The handler retrieves the narrative text from the event context. It uses regular expressions to look for patterns of the form:
  - `key=somekey`
  - `value=somevalue`
The separator between key/value and their contents can be any character except alphanumerics, underscores, asterisks, hashes, or whitespace. For example, `key=blood_pressure*value=120/80`.
**Action Taken**
If both a key and value are found in the narrative:
  - It logs an informational message including the patient id, key, and value.
  - It returns a list containing one Effect: an upsert (create or update) of patient metadata (via PatientMetadata) for that patient, saving the found key and value.
If either the key or value is missing, no action is performed.
    ```python
    import re
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.patient_metadata import PatientMetadata
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from logger import log
    class MyHandler(BaseHandler):
        """
        Extracts key-value pairs from plan update narratives and stores them as patient metadata.
        Parses narrative text for patterns like "key=somekey*value=somevalue" where the separator
        can be any non-alphanumeric character. If both key and value are found, creates or updates
        the corresponding patient metadata entry.
        Triggers on: PLAN_COMMAND__POST_UPDATE events
        Effects: PatientMetadata upsert operations
        """
        RESPONDS_TO = EventType.Name(EventType.PLAN_COMMAND__POST_UPDATE)
        def compute(self) -> list[Effect]:
            """This method gets called when an event of the type RESPONDS_TO is fired."""
            patient_id = self.context["patient"]["id"]
            fields = self.context.get("fields", {})
            narrative = fields.get("narrative", "")
            key_match = re.search(r"key=([^*#_\s]+)", narrative)
            value_match = re.search(r"value=([^*#_\s]+)", narrative)
            key = key_match.group(1) if key_match else None
            value = value_match.group(1) if value_match else None
            log.info(
                f"Upserting patient metadata for patient {patient_id} with key: {key} and value: {value}"
            )
            if not key or not value:
                return []
            return [PatientMetadata(patient_id=patient_id, key=str(key)).upsert(str(value))]
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/example-upsert_patient_metadata/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/example-vitals_visualizer_plugin/
> **Note:** [View the source](https://github.com/canvas-medical/canvas-plugins/tree/main/example-plugins/vitals_visualizer_plugin) for this plugin on GitHub. 
A Canvas plugin that displays a "Visualize" button in the vitals section of the chart summary and shows interactive vital signs visualizations.
##  SDK Features 
  - Creates and responds to a [Simple API](/sdk/handlers-simple-api-http/) endpoint that [renders a template](/sdk/layout-effect/#custom-html-and-django-templates) and returns HTML content
  - Parses [Observation data model](/sdk/data-observation/) to obtain vitals data and format into readable content
  - Adds a "Visualize" [Action Button](/sdk/handlers-action-buttons/) to the vitals section of the patient chart summary.
  - Returns a [LaunchModalEffect](/sdk/layout-effect/#modals) in the right chart pane with content from the vitals endpoint.
##  Configuration 
This SimpleAPI endpoint uses the [StaffSessionAuthMixin](/sdk/handlers-simple-api-http/#staff-session)
##  Structure 
    ```plaintext
    vitals_visualizer_plugin/
    ├── handlers/
    │   ├── __init__.py
    │   ├── vitals_button.py      # Action button handler
    │   └── vitals_api.py         # API endpoint with visualization
    ├── templates/
    │   └── vitals_visualization.html # HTML template for visualization UI
    ├── CANVAS_MANIFEST.json      # Plugin configuration
    └── README.md                 # Documentation
    ```
##  CANVAS_MANIFEST.json 
    ```json
    {
        "sdk_version": "0.1.4",
        "plugin_version": "0.1.0",
        "name": "vitals_visualizer_plugin",
        "description": "A plugin that adds visualization capabilities to patient vital signs in the chart summary",
        "components": {
            "handlers": [
                {
                    "class": "vitals_visualizer_plugin.handlers.vitals_button:VitalsVisualizerButton",
                    "description": "A button that opens vitals visualization modal",
                    "data_access": {
                        "event": "SHOW_CHART_SUMMARY_VITALS_SECTION_BUTTON",
                        "read": ["v1.Observation"],
                        "write": []
                    }
                },
                {
                    "class": "vitals_visualizer_plugin.handlers.vitals_api:VitalsVisualizerAPI",
                    "description": "API endpoint that serves vitals visualization data and UI",
                    "data_access": {
                        "event": "",
                        "read": ["v1.Observation"],
                        "write": []
                    }
                }
            ],
            "commands": [],
            "content": [],
            "effects": [],
            "views": []
        },
        "variables": [],
        "tags": [],
        "license": "NONE",
        "readme": "./README.md"
    }
    ```
##  templates/ 
###  vitals_visualization.html 
This is the html template called by the[ `render_to_string` function](/sdk/layout-effect/#custom-html-and-django-templates) with a `vitals_data` object. It contains styling and custom JavaScript to form the chart.
##  handlers/ 
###  vitals_api.py 
The `vitals_api.py` file defines a class-based API endpoint called `VitalsVisualizerAPI` for a Canvas Medical plugin. This endpoint provides both a user interface (HTML) and data (as JSON) for visualizing patient vitals—specifically weight, body temperature, and oxygen saturation.
  - **API Endpoint** : The endpoint is accessed at `/visualize`, and requires staff authentication via `StaffSessionAuthMixin`.
  - **Request Handling** : The main entry point is the `get()` method, which expects a `patient_id` as a query parameter. 
    - If `patient_id` is missing, it returns a 400 JSON error response.
    - If present, it fetches and compiles the patient's vital sign data, then generates and returns an HTML interface for visualization.
  - **Vitals Data Extraction** : The `_get_vitals_data(patient_id)` method: 
    - Queries Canvas Medical's `Observation` resource for the specified patient.
    - Filters for observations in the "vital-signs" category that are not deleted, not entered in error, and are not "Vital Signs Panel" summary records.
    - Extracts individual observations for: 
      - **Weight** (converting from ounces to pounds),
      - **Body Temperature** (with default units °F if missing),
      - **Oxygen Saturation** (with default units % if missing).
    - Organizes the data into a dictionary keyed by vital sign name, each containing a list of timestamped values.
  - **HTML Visualization Rendering** : The `_generate_visualization_html(vitals_data)` method: 
    - Serializes the vitals data as JSON.
    - Passes it as context to a template called `vitals_visualization.html` for rendering the UI.
  - **Error Handling and Logging** : All major steps have try/except blocks, logging errors and returning JSON error responses if needed.
**How the Components Work Together**
  - The endpoint provides a combined UI/data interface for staff to review prescribed vitals over time for a selected patient.
  - The code is built for integration into the Canvas Medical system utilizing their SDK, data models, and permission system.
  - All HTML and logic for visualization are rendered server-side using a templating mechanism.
**Dependencies and Assumptions**
  - Depends on `Observation` model/data from Canvas Medical's API SDK.
  - Uses Canvas SDK facilities for routing, API responses, session authentication, and template rendering.
  - Relies on templates being in the `templates/` folder, specifically one for vitals visualization.
  - Expects a structured logging setup.
**Returned Data Types**
  - Templated HTML (for visualization) or JSON (for errors), wrapped in Canvas SDK response objects (`HTMLResponse`, `JSONResponse`).
    ```python
    import json
    from typing import Any, Dict, List
    from canvas_sdk.effects.simple_api import HTMLResponse, JSONResponse
    from canvas_sdk.handlers.simple_api import SimpleAPIRoute, StaffSessionAuthMixin
    from canvas_sdk.templates import render_to_string
    from canvas_sdk.v1.data import Observation
    from logger import log
    class VitalsVisualizerAPI(StaffSessionAuthMixin, SimpleAPIRoute):
        """API endpoint that serves vitals visualization data and UI."""
        PATH = "/visualize"
        def get(self) -> list[HTMLResponse | JSONResponse]:
            """Return the vitals visualization UI and data."""
            patient_id = self.request.query_params.get("patient_id")
            if not patient_id:
                return [JSONResponse({"error": "Patient ID is required"}, status_code=400)]
            try:
                vitals_data = self._get_vitals_data(patient_id)
                html_content = self._generate_visualization_html(vitals_data)
                return [HTMLResponse(content=html_content)]
            except Exception as e:
                log.error(f"Error in VitalsVisualizerAPI: {str(e)}")
                return [JSONResponse({"error": str(e)}, status_code=500)]
        def _get_vitals_data(self, patient_id: str) -> Dict[str, List[Dict[str, Any]]]:
            """Get vitals data for the patient using Canvas vitals structure."""
            try:
                # Get individual vital observations from vital signs panels
                vital_observations = (
                    Observation.objects.for_patient(patient_id)
                    .filter(
                        category="vital-signs",
                        effective_datetime__isnull=False,
                        deleted=False,
                    )
                    .exclude(name="Vital Signs Panel")
                    .exclude(entered_in_error__isnull=False)
                    .select_related("is_member_of")
                    .order_by("effective_datetime")
                )
                vitals_data = {
                    "weight": [],
                    "body_temperature": [],
                    "oxygen_saturation": [],
                }
                for obs in vital_observations:
                    if not obs.value or obs.name in ["note", "pulse_rhythm"]:
                        continue
                    if obs.name == "weight":
                        try:
                            value_oz = float(obs.value)
                            value_lbs = value_oz / 16
                            vitals_data["weight"].append(
                                {
                                    "date": obs.effective_datetime.isoformat(),
                                    "value": round(value_lbs, 1),
                                    "units": "lbs",
                                }
                            )
                        except (ValueError, TypeError):
                            continue
                    elif obs.name == "body_temperature":
                        try:
                            value = float(obs.value)
                            vitals_data["body_temperature"].append(
                                {
                                    "date": obs.effective_datetime.isoformat(),
                                    "value": value,
                                    "units": obs.units or "°F",
                                }
                            )
                        except (ValueError, TypeError):
                            continue
                    elif obs.name == "oxygen_saturation":
                        try:
                            value = float(obs.value)
                            vitals_data["oxygen_saturation"].append(
                                {
                                    "date": obs.effective_datetime.isoformat(),
                                    "value": value,
                                    "units": obs.units or "%",
                                }
                            )
                        except (ValueError, TypeError):
                            continue
                return vitals_data
            except Exception as e:
                log.error(f"Error collecting vitals data: {str(e)}")
                return {"weight": [], "body_temperature": [], "oxygen_saturation": []}
        def _generate_visualization_html(
            self, vitals_data: Dict[str, List[Dict[str, Any]]]
        ) -> str:
            """Generate the HTML for the vitals visualization using template."""
            context = {"vitals_data": json.dumps(vitals_data)}
            return render_to_string("templates/vitals_visualization.html", context)
    ```
###  vitals_button.py 
This file adds a "Visualize" button to the patient chart's vitals section, which, when clicked, opens a modal with a visual representation of the patient's vitals data, using the Canvas plugin and effect framework.
**Class Details**
  - The class `VitalsVisualizerButton` inherits from `ActionButton`.
  - It specifies metadata such as title ("Visualize"), a unique key (`vitals_visualizer_button`), the section of the UI where it appears (vitals summary), and its priority (1).
**Functionality**
  - When a user clicks the button, the `handle` method is triggered.
  - This method constructs a URL to an API endpoint for visualizing vitals: `/plugin-io/api/vitals_visualizer_plugin/visualize?patient_id=<id>`, where `<id>` is the current patient ID.
  - The button initiates a `LaunchModalEffect`, opening a large modal on the right side of the chart pane with the title "Vitals Visualization".
  - The modal displays the contents served by the constructed URL (likely a vitals graph or dashboard).
**Integration with Canvas SDK**
  - Uses SDK classes: 
    - `ActionButton` for button behaviors and placement.
    - `LaunchModalEffect` to open a modal window within the Canvas UI.
    - The effect is returned in a list, as required by the SDK's handler pattern.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.launch_modal import LaunchModalEffect
    from canvas_sdk.handlers.action_button import ActionButton
    class VitalsVisualizerButton(ActionButton):
        """A button that opens the vitals visualization modal."""
        BUTTON_TITLE = "Visualize"
        BUTTON_KEY = "vitals_visualizer_button"
        BUTTON_LOCATION = ActionButton.ButtonLocation.CHART_SUMMARY_VITALS_SECTION
        PRIORITY = 1
        def handle(self) -> list[Effect]:
            """Handle button click by opening vitals visualization modal."""
            # The API endpoint will be at /plugin-io/api/vitals_visualizer_plugin/visualize
            # We need to pass the patient ID in the URL
            patient_id = self.target
            return [
                LaunchModalEffect(
                    url=f"/plugin-io/api/vitals_visualizer_plugin/visualize?patient_id={patient_id}",
                    target=LaunchModalEffect.TargetType.RIGHT_CHART_PANE_LARGE,
                    title="Vitals Visualization"
                ).apply()
            ]
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/example-vitals_visualizer_plugin/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/examples/
The pages below showcase example plugins written with the Canvas SDK. All pages describe the file structure, the functionality, and link to GitHub where you can grab the code yourself and start iterating.
[ API Samples ](/sdk/example-api_samples/) [ Charting Interface ]() [ Patient Portal Customization ]() [ Integrations ]()
----- END PAGE https://docs.canvasmedical.com/sdk/examples/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/handlers-action-buttons/
Action buttons are UI elements that trigger specific actions when clicked in the Canvas UI. These buttons can be placed in different locations and can interact with runtime data to execute custom code.
##  Overview 
An `ActionButton` class allows you to define custom buttons that appear in different sections of the Canvas UI. When a user clicks the button, the action associated with the button is executed. Action buttons can be added to various locations in the UI, and you can control their visibility and behavior through effects in a handler class.
There are no limitations on the number of action buttons you can create. You can define multiple buttons in a single handler class or create separate classes for each button.
##  Creating an action button 
An action button is a [handler](/sdk/handlers-basehandler/). Subclass `ActionButton`, say where it goes with `BUTTON_LOCATION`, and implement `handle()` to say what a click does:
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.launch_modal import LaunchModalEffect
    from canvas_sdk.handlers.action_button import ActionButton
    class PatientSummaryButton(ActionButton):
        BUTTON_TITLE = "Summary"
        BUTTON_KEY = "PATIENT_SUMMARY"
        BUTTON_LOCATION = ActionButton.ButtonLocation.CHART_PATIENT_HEADER
        def handle(self) -> list[Effect]:
            return [
                LaunchModalEffect(
                    target=LaunchModalEffect.TargetType.DEFAULT_MODAL,
                    content="<html>Your content here</html>",
                ).apply()
            ]
    ```
Then register the class under `handlers` in your `CANVAS_MANIFEST.json`:
    ```json
    {
      "components": {
        "handlers": [
          {
            "class": "my_plugin.buttons.patient_summary:PatientSummaryButton",
            "description": "Shows a summary modal from the patient header."
          }
        ]
      }
    }
    ```
###  Class attributes 
Attribute | Required | Description  
---|---|---  
`BUTTON_TITLE` | Required | The label shown on the button. Emoji are supported.  
`BUTTON_KEY` | Required | A unique identifier for the button. Canvas routes the click back to your `handle()` by this value, so give each button its own.  
`BUTTON_LOCATION` | Required | Where the button appears, as a `ButtonLocation` value.  
`PRIORITY` | Optional | Orders this button against others in the same location, lower first. Defaults to `0`, and buttons sharing a priority have no guaranteed order.  
`BUTTON_TEXT_COLOR` | Optional | The label colour as a HEX value, for example `"#FF0000"`. Defaults to whichever of black or white contrasts with the background.  
`BUTTON_BACKGROUND_COLOR` | Optional | The background colour as a HEX value, for example `"#4CAF50"`. Defaults to Canvas's grey button styling.  
###  Methods 
Method | Returns | Required | Description  
---|---|---|---  
`handle()` | `list[Effect]` | Required | Runs when the button is clicked. Return the [effects](/sdk/effects/) the click should produce, or an empty list for none.  
`visible()` | `bool` | Optional | Runs each time the button's location loads, to decide whether the button is shown. Defaults to `True`. See Dynamic, state-responsive buttons.  
    ```python
        def visible(self) -> bool:
            """Only offer the button on an encounter note."""
            note_id = self.event.context.get("note_id")
            return Note.objects.filter(dbid=note_id, note_type_version__category="encounter").exists()
    ```
###  Button locations 
The `ActionButton` class defines several locations where the button can be placed. The location is defined using the `ButtonLocation` enum. Supported button locations include:
**Location** | **Description**  
---|---  
`NOTE_HEADER` | The button will appear in the header of each note.  
`NOTE_FOOTER` | The button will appear in the footer of each note.  
`NOTE_HEADER_DROPDOWN` | The button will appear in the note header dropdown.  
`CHART_PATIENT_HEADER` | The button will appear in the patient header on both the chart and profile pages.  
`CHART_SUMMARY_SOCIAL_DETERMINANTS_SECTION` | The button will appear in the Social Determinants section of the chart summary.  
`CHART_SUMMARY_GOALS_SECTION` | The button will appear in the Goals section of the chart summary.  
`CHART_SUMMARY_CONDITIONS_SECTION` | The button will appear in the Conditions section of the chart summary.  
`CHART_SUMMARY_MEDICATIONS_SECTION` | The button will appear in the Medications section of the chart summary.  
`CHART_SUMMARY_ALLERGIES_SECTION` | The button will appear in the Allergies section of the chart summary.  
`CHART_SUMMARY_CARE_TEAMS_SECTION` | The button will appear in the Care Teams section of the chart summary.  
`CHART_SUMMARY_VITALS_SECTION` | The button will appear in the Vitals section of the chart summary.  
`CHART_SUMMARY_IMMUNIZATIONS_SECTION` | The button will appear in the Immunizations section of the chart summary.  
`CHART_SUMMARY_SURGICAL_HISTORY_SECTION` | The button will appear in the Surgical History section of the chart summary.  
`CHART_SUMMARY_FAMILY_HISTORY_SECTION` | The button will appear in the Family History section of the chart summary.  
`CHART_SUMMARY_CODING_GAPS_SECTION` | The button will appear in the Coding Gaps section of the chart summary.  
`NOTE_BODY_AUTOMATION` | The button appears as an entry in the note body's "/" (slash) command list while a clinician documents a note.  
##  Dynamic, state-responsive buttons 
Action buttons are not rendered once and cached. Canvas re-evaluates every `ActionButton` handler each time its location loads — and again whenever the location is reloaded. On each evaluation Canvas fires the [`SHOW_*_BUTTON`](/sdk/events/#action-buttons-events) event for that location and your handler's `visible()` method decides whether the button is included.
This is what makes buttons _dynamic_ : because `visible()` runs against live data every time, the same button can appear, disappear, or change its title depending on the note, the patient, or the logged-in user.
###  Reading the runtime context 
Two different events reach an `ActionButton`, and they do not carry the same context. `visible()` runs on the [`SHOW_*_BUTTON`](/sdk/events/#action-buttons-events) event for the button's location, while `handle()` runs on `ACTION_BUTTON_CLICKED` once the button is clicked. Read both through `self.event`.
####  While deciding visibility 
What `visible()` sees depends on where the button lives:
Accessor | Note and header locations | Chart summary sections  
---|---|---  
`self.event.target.id` | The id of the patient the button is rendered for | The id of the patient  
`self.event.context["user"]` | The logged-in user, as `{"type": "Staff", "id": "<staff id>"}` | The same  
`self.event.context["note_id"]` | The **database id** of the note, or `None` on a location that has no note | Not present  
"Note and header locations" means `NOTE_HEADER`, `NOTE_FOOTER`, `NOTE_BODY`, `NOTE_BODY_AUTOMATION`, `NOTE_HEADER_DROPDOWN` and `CHART_PATIENT_HEADER`. Every `CHART_SUMMARY_*_SECTION` location gets the patient and the user only, because a chart summary is not rendered inside a note.
`note_id` is a database id rather than a UUID, so look the note up with `dbid`:
    ```python
        def visible(self) -> bool:
            note_id = self.event.context["note_id"]
            return Note.objects.filter(dbid=note_id, note_type_version__category="encounter").exists()
    ```
####  When the button is clicked 
Accessor | Value  
---|---  
`self.event.context["key"]` | The `BUTTON_KEY` of the button that was clicked. Canvas uses this to route the click, so `handle()` only runs for your own.  
`self.event.context["user"]` | The logged-in user, as `{"type": "Staff", "id": "<staff id>"}`  
`self.event.context["note_id"]` | The database id of the note the button was clicked from. Present only when the button lives on a note.  
`self.event.context["line_number"]` | The note body line the clinician typed the trigger on. Present only for a `NOTE_BODY_AUTOMATION` entry.  
`self.event.target.id` | The id of the patient the button was clicked from  
###  Reloading buttons 
Because visibility is computed from live data, Canvas needs to know _when_ to re-evaluate it. A button's location is reloaded automatically after its own `handle()` runs, but you will often want to reload in response to something else changing — for example, recomputing the footer after a command is committed, or after the note transitions to a new state.
Return one of these [Reload Action Buttons](/sdk/effect-reload-action-buttons/) effects (imported from `canvas_sdk.effects.action_button`) from any handler's `handle()` or `compute()` to refresh a location's buttons:
Effect | Re-evaluates  
---|---  
`ReloadNoteActionButtonsEffect(id=<note id>)` | Every button bound to that note  
`ReloadPatientActionButtonsEffect(id=<patient id>)` | Every button bound to that patient  
A reload re-fires the [`SHOW_*_BUTTON`](/sdk/events/#action-buttons-events) events, so every button recomputes `visible()` from scratch — the button set is rebuilt rather than patched. Any handler can emit a reload, not just an `ActionButton`; Example 4 below uses plain event handlers to keep the footer in sync as the note changes.
##  Note body automations 
A note body automation puts your plugin's own entry in the note body's "/" (slash) menu, the inline list clinicians use to insert commands while documenting a note. The location exists so an automation can come from a plugin rather than only from Canvas: one entry standing in for the several [commands](/sdk/commands/) a workflow would otherwise have the clinician insert by hand.
    ```python
    from canvas_sdk.commands import PlanCommand, TaskCommand
    from canvas_sdk.commands.commands.task import AssigneeType, TaskAssigner
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.batch_originate import BatchOriginateCommandEffect
    from canvas_sdk.handlers.action_button import ActionButton
    from canvas_sdk.v1.data.note import Note
    class LipidPanelAutomation(ActionButton):
        BUTTON_TITLE = "Order lipid panel follow-up"
        BUTTON_KEY = "LIPID_PANEL_AUTOMATION"
        BUTTON_LOCATION = ActionButton.ButtonLocation.NOTE_BODY_AUTOMATION
        def visible(self) -> bool:
            # Offer the entry only to the note's own provider.
            note_id = self.event.context.get("note_id")
            user_id = (self.event.context.get("user") or {}).get("id")
            if not note_id or not user_id:
                return False
            return Note.objects.filter(dbid=note_id, provider__id=user_id).exists()
        def handle(self) -> list[Effect]:
            note = Note.objects.filter(dbid=self.event.context["note_id"]).first()
            if note is None:
                return []
            plan = PlanCommand(
                note_uuid=str(note.id),
                narrative="Recheck lipid panel in 3 months",
            )
            task = TaskCommand(
                note_uuid=str(note.id),
                title="Call patient with lipid panel results",
                assign_to=TaskAssigner(to=AssigneeType.STAFF, id=note.provider.dbid),
            )
            # One batch, so both commands reach the note in a single update.
            return [
                BatchOriginateCommandEffect(
                    commands=[plan, task],
                    replace_line=True,
                ).apply()
            ]
    ```
It is an ordinary action button, with no separate automation class. Set `BUTTON_LOCATION` to `NOTE_BODY_AUTOMATION`, give it the usual `BUTTON_TITLE`, `BUTTON_KEY` and `PRIORITY`, and scope it with `visible()`. Canvas asks each plugin for its entries through the [`SHOW_NOTE_BODY_AUTOMATION_BUTTON`](/sdk/events/#action-buttons-events) event, whose context carries the signed-in staff member alongside the note and the patient, so an entry can be limited to particular staff as well as to particular notes or patients.
###  How Canvas renders the entry 
Behaviour | Detail  
---|---  
Position | After the native commands, ordered by `PRIORITY` then title  
Marker | A plug icon, which distinguishes it from Canvas's native automations  
When the list is built | Once, as the note loads, rather than on every keystroke  
Filtering | Client-side against `BUTTON_TITLE` as the clinician types  
The trigger line | Cleared as soon as the entry is selected, whatever `handle()` returns  
###  What `handle()` can return 
An entry is not limited to originating commands. `handle()` may return any effects, or none at all. What the location is meant for is the first row:
Returned from `handle()` | Where the commands land  
---|---  
A [`BatchOriginateCommandEffect`](/sdk/effect-batch-originate/) with `replace_line=True` | On the line the clinician typed the trigger on, replacing it, with no trailing trigger text or blank-line padding. Canvas supplies the position, so leave `line_number` unset.  
A `BatchOriginateCommandEffect` without `replace_line` | At the bottom of the note, from the effect's own `line_number=-1` default  
A single command's `originate()` | Wherever that effect places it. One command needs no batch  
Anything else, or nothing | Nothing is added to the note, and the trigger line is still cleared  
Reach for the batch whenever an entry originates **more than one** command. It updates the note body once for the whole group rather than once per command, which is faster and keeps the group together: separate originate effects each update the note on their own, so they can interleave with other writes and land out of order.
An entry does not have to write to the note at all. Because it is an ordinary action button, it can put any tool of yours behind the "/" menu, which is a shorter reach for a clinician mid-note than the app drawer. This one opens a risk calculator, and writes nothing:
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.launch_modal import LaunchModalEffect
    from canvas_sdk.handlers.action_button import ActionButton
    from canvas_sdk.templates import render_to_string
    class AscvdRiskCalculator(ActionButton):
        BUTTON_TITLE = "ASCVD risk calculator"
        BUTTON_KEY = "ASCVD_RISK_CALCULATOR"
        BUTTON_LOCATION = ActionButton.ButtonLocation.NOTE_BODY_AUTOMATION
        def handle(self) -> list[Effect]:
            return [
                LaunchModalEffect(
                    target=LaunchModalEffect.TargetType.DEFAULT_MODAL,
                    content=render_to_string("templates/ascvd_calculator.html"),
                    title="ASCVD risk",
                ).apply()
            ]
    ```
The clinician types `/`, picks the calculator, and it opens over the note. The trigger line is cleared either way, so the note is left exactly as it was.
##  Examples 
###  Log information when a button is clicked 
This example demonstrates a simple action button that logs some information when clicked. The button is visible only during the month of January.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.handlers.action_button import ActionButton
    from datetime import datetime
    from logger import log
    class MyButton(ActionButton):
        BUTTON_TITLE = "🪵 Log Action"
        BUTTON_KEY = "LOG_ACTION"
        BUTTON_LOCATION = ActionButton.ButtonLocation.NOTE_HEADER
        def visible(self) -> bool:
            # Only show this button in January
            return datetime.now().month == 1
        def handle(self) -> list[Effect]:
            log.info("Button clicked!")
            log.info(self.event.context)
            log.info(self.event.target)
            return []
    ```
###  Commit every command in a note 
This example demonstrates an action button in the note footer that commits all commands within a note. The button is always visible since the `visible()` method is not overridden.
    ```python
    import json
    from canvas_sdk.effects import Effect
    from canvas_sdk.handlers.action_button import ActionButton
    from canvas_sdk.v1.data.command import Command
    from canvas_sdk.effects.base import EffectType
    # Define a mapping of schema_key to EffectType
    schema_key_to_effect_type = {
        "allergy": EffectType.COMMIT_ALLERGY_COMMAND,
        "assess": EffectType.COMMIT_ASSESS_COMMAND,
        "changeMedication": EffectType.COMMIT_CHANGE_MEDICATION_COMMAND,
        "closeGoal": EffectType.COMMIT_CLOSE_GOAL_COMMAND,
        "diagnose": EffectType.COMMIT_DIAGNOSE_COMMAND,
        "familyHistory": EffectType.COMMIT_FAMILY_HISTORY_COMMAND,
        "goal": EffectType.COMMIT_GOAL_COMMAND,
        "instruct": EffectType.COMMIT_INSTRUCT_COMMAND,
        "hpi": EffectType.COMMIT_HPI_COMMAND,
        "medicalHistory": EffectType.COMMIT_MEDICAL_HISTORY_COMMAND,
        "medicationStatement": EffectType.COMMIT_MEDICATION_STATEMENT_COMMAND,
        "perform": EffectType.COMMIT_PERFORM_COMMAND,
        "plan": EffectType.COMMIT_PLAN_COMMAND,
        "questionnaire": EffectType.COMMIT_QUESTIONNAIRE_COMMAND,
        "reasonForVisit": EffectType.COMMIT_REASON_FOR_VISIT_COMMAND,
        "removeAllergy": EffectType.COMMIT_REMOVE_ALLERGY_COMMAND,
        "stopMedication": EffectType.COMMIT_STOP_MEDICATION_COMMAND,
        "surgicalHistory": EffectType.COMMIT_SURGICAL_HISTORY_COMMAND,
        "task": EffectType.COMMIT_TASK_COMMAND,
        "updateDiagnosis": EffectType.COMMIT_UPDATE_DIAGNOSIS_COMMAND,
        "updateGoal": EffectType.COMMIT_UPDATE_GOAL_COMMAND,
        "vitals": EffectType.COMMIT_VITALS_COMMAND,
    }
    class CommitButtonHandler(ActionButton):
        BUTTON_TITLE = "Commit all commands"
        BUTTON_KEY = "COMMIT_ALL_COMMANDS"
        BUTTON_LOCATION = ActionButton.ButtonLocation.NOTE_FOOTER
        def handle(self) -> list[Effect]:
            note_id = self.context.get("note_id")
            effects = []
            for command in Command.objects.filter(note_id=note_id):
                effect_type = schema_key_to_effect_type.get(command.schema_key)
                if not effect_type:
                    raise ValueError(f"No EffectType defined for schema key '{command.schema_key}'.")
                effects.append(
                    Effect(
                        type=effect_type,
                        payload=json.dumps({"command": str(command.id)}),
                    )
                )
            return effects
    ```
###  Render HTML from a chart summary section 
In this example, we place a button in the Vitals section and define an action where the button, when clicked, displays custom HTML content to the user. For more info about `LaunchModalEffect`, check the [documentation](/sdk/layout-effect/#modals).
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.handlers.action_button import ActionButton
    from canvas_sdk.effects.launch_modal import LaunchModalEffect
    from logger import log
    class VitalsButtonHandler(ActionButton):
        BUTTON_TITLE = "📊 Show Vitals Info"
        BUTTON_KEY = "SHOW_VITALS_INFO"
        BUTTON_LOCATION = ActionButton.ButtonLocation.CHART_SUMMARY_VITALS_SECTION
        def handle(self) -> list[Effect]:
            # This method will be called when the button is clicked
            log.info("Vitals info button clicked!")
            # Custom HTML content to display
            custom_html = """
            <div style="padding: 20px; background-color: #f0f8ff; border-radius: 5px;">
                <h3>Vitals Information</h3>
                <p>Patient's latest vitals data:</p>
                <ul>
                    <li>Heart Rate: 72 bpm</li>
                    <li>Blood Pressure: 120/80 mmHg</li>
                    <li>Respiratory Rate: 16 breaths/min</li>
                    <li>Temperature: 98.6°F</li>
                </ul>
                <p>For more details, please refer to the full report.</p>
            </div>
            """
            # Return a LaunchModalEffect to show the custom HTML content in a modal
            return [LaunchModalEffect(
                target=LaunchModalEffect.TargetType.DEFAULT_MODAL,
                content=custom_html
            ).apply()]
        def visible(self) -> bool:
            # Optionally, make the button visible only under specific conditions
            return True
    ```
##  Note state action buttons 
`NoteStateActionButton` is a specialized `ActionButton` subclass for note footer buttons that transition a note from one state to another — locking, signing, pushing charges, deleting, and discharging, along with the appointment transitions check in, no show, cancel, and restore. It handles visibility, ordering, and the underlying state-transition effect for you, so a plugin can replace Canvas's default footer buttons with its own.
To create one, subclass `NoteStateActionButton` and set the `STATE_ACTION` class attribute to the target [`NoteStates`](/sdk/data-note/#notestates) value the button should transition the note into. Locking and signing carry extra rules, so the SDK also ships two ready-to-use subclasses — `LockNoteActionButton` and `SignNoteActionButton` — that you can register directly (or subclass) instead of setting `STATE_ACTION` yourself:
    ```python
    from canvas_sdk.handlers.action_button import (
        LockNoteActionButton,
        NoteStateActionButton,
        SignNoteActionButton,
    )
    from canvas_sdk.v1.data.note import NoteStates
    # Lock and Sign subclass the specialized bases — STATE_ACTION and their extra
    # rules are already set on those classes.
    class LockNoteButton(LockNoteActionButton):
        pass
    class SignNoteButton(SignNoteActionButton):
        pass
    # Every other transition subclasses NoteStateActionButton and sets STATE_ACTION.
    class UnlockNoteButton(NoteStateActionButton):
        STATE_ACTION = NoteStates.UNLOCKED
    class PushChargesNoteButton(NoteStateActionButton):
        STATE_ACTION = NoteStates.PUSHED
    class CheckInAppointmentButton(NoteStateActionButton):
        STATE_ACTION = NoteStates.CONVERTED
    class NoShowAppointmentButton(NoteStateActionButton):
        STATE_ACTION = NoteStates.NOSHOW
    class CancelAppointmentButton(NoteStateActionButton):
        STATE_ACTION = NoteStates.CANCELLED
    class RestoreAppointmentButton(NoteStateActionButton):
        STATE_ACTION = NoteStates.REVERTED
    class DeleteNoteButton(NoteStateActionButton):
        STATE_ACTION = NoteStates.DELETED
    class RestoreNoteButton(NoteStateActionButton):
        STATE_ACTION = NoteStates.UNDELETED
    class DischargeNoteButton(NoteStateActionButton):
        STATE_ACTION = NoteStates.DISCHARGED
    ```
Register each button as a handler in your `CANVAS_MANIFEST.json`, exactly like any other `ActionButton`.
Each button is configured automatically from its `STATE_ACTION`, so a subclass normally sets nothing else:
Attribute | Value  
---|---  
`BUTTON_LOCATION` | Always `NOTE_FOOTER`. Not overridable.  
`BUTTON_TITLE` | An imperative label for the target state, from the table below. Set it explicitly to override.  
`BUTTON_KEY` | `note_state_action__<state value>`, for example `note_state_action__LKD`. Set it explicitly to override.  
`visible()` | Implemented by the base class, which shows the button only when the transition is permitted. See Visibility.  
These are the supported transitions:
`STATE_ACTION` | Subclass | Default title | Default key  
---|---|---|---  
`NoteStates.LOCKED` | `LockNoteActionButton` | `Lock` | `note_state_action__LKD`  
`NoteStates.SIGNED` | `SignNoteActionButton` | `Sign` | `note_state_action__SGN`  
`NoteStates.UNLOCKED` | `NoteStateActionButton` | `Unlock` | `note_state_action__ULK`  
`NoteStates.PUSHED` | `NoteStateActionButton` | `Push charges` | `note_state_action__PSH`  
`NoteStates.DISCHARGED` | `NoteStateActionButton` | `Discharge` | `note_state_action__DSC`  
`NoteStates.DELETED` | `NoteStateActionButton` | `Delete` | `note_state_action__DLT`  
`NoteStates.UNDELETED` | `NoteStateActionButton` | `Restore` | `note_state_action__UND`  
`NoteStates.CONVERTED` | `NoteStateActionButton` | `Check in` | `note_state_action__CVD`  
`NoteStates.NOSHOW` | `NoteStateActionButton` | `No show` | `note_state_action__NSW`  
`NoteStates.CANCELLED` | `NoteStateActionButton` | `Cancel` | `note_state_action__CLD`  
`NoteStates.REVERTED` | `NoteStateActionButton` | `Restore` | `note_state_action__RVT`  
`Cancel` and `Restore` act on the note's appointment rather than on the note itself, so they do nothing on a note that has no appointment. `Sign` locks the note first when it is not already locked.
When a button is clicked, Canvas applies the transition's effect and reloads the footer so it reflects the note's new state.
###  Visibility 
A `NoteStateActionButton` appears only when its `STATE_ACTION` is a permitted transition from the note's current state and note type, so you don't need to override `visible()` yourself. When several are visible at once, Canvas orders them to match the order it offers the transitions for the current state.
Three buttons carry extra gates on top of that, which the base class applies for you:
Button | Also shown only when  
---|---  
`LockNoteActionButton` | The note type does **not** require a signature  
`SignNoteActionButton` | The note type **does** require a signature, and the current user has not signed since the last lock  
`Discharge` | The note type is an inpatient one  
Lock and Sign are the same underlying transition, split by whether the note type requires a signature. Because Sign hides itself only for the user who signed, a note can be signed by several users in turn, and it is re-locked only after an amend.
###  Replacing Canvas's default footer buttons 
Your state buttons appear _alongside_ Canvas's built-in state-transition buttons by default. To hide the native ones so yours replace them, answer the `NOTE_FOOTER__GET_CONFIGURATION` event with a [`NoteFooterConfiguration`](/sdk/effect-note-footer-configuration/) effect. Footer suppression is configured once per note (not per button):
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.note_footer_configuration import NoteFooterConfiguration
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers.base import BaseHandler
    class HideDefaultStateButtons(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.NOTE_FOOTER__GET_CONFIGURATION)
        def compute(self) -> list[Effect]:
            return [NoteFooterConfiguration(hide_default_state_buttons=True).apply()]
    ```
###  Customizing when a button appears 
Override `visible()` to layer your own rules on top of the built-in checks — call `super().visible()` first so you keep everything the base already enforces, then add your conditions. Subclassing `SignNoteActionButton` means `super().visible()` still applies the sign-specific rules (signature-required, lock-first, and already-signed). This Sign button additionally hides itself while the note has staged (uncommitted) commands, because a note can't be signed until its commands are committed (reason-for-visit is auto-managed and doesn't block signing, so it's excluded):
    ```python
    from canvas_sdk.handlers.action_button import SignNoteActionButton
    from canvas_sdk.v1.data.command import Command
    class SignNoteButton(SignNoteActionButton):
        def visible(self) -> bool:
            if not super().visible():
                return False
            note_id = self.event.context.get("note_id")
            return not (
                Command.objects.filter(note_id=note_id, state="staged")
                .exclude(schema_key="reasonForVisit")
                .exists()
            )
    ```
You can gate on anything in the runtime context. For example, to show a button only to the note's provider, compare the logged-in user against the note's provider (`note.provider.id` and the user id are both Staff ids):
    ```python
        def visible(self) -> bool:
            if not super().visible():
                return False
            note_id = self.event.context.get("note_id")
            user_id = (self.event.context.get("user") or {}).get("id")
            if not note_id or not user_id:
                return False
            return Note.objects.filter(dbid=note_id, provider__id=user_id).exists()
    ```
###  Keeping the footer in sync 
`visible()` is only re-evaluated when the footer is reloaded. A transition triggered by one of these buttons reloads the footer automatically, but changes from elsewhere don't — so pair the buttons with handlers that reload the footer when the note changes by another path. For example, reload whenever a command is committed (so the Sign button reappears the instant the last command is committed) and whenever the note's state changes:
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.action_button import ReloadNoteActionButtonsEffect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers.base import BaseHandler
    from canvas_sdk.v1.data.command import Command
    class ReloadFooterOnCommandCommit(BaseHandler):
        """Reload the footer whenever any command is committed."""
        RESPONDS_TO = [
            EventType.Name(value)
            for value in EventType.values()
            if EventType.Name(value).endswith("_COMMAND__POST_COMMIT")
        ]
        def compute(self) -> list[Effect]:
            command = Command.objects.filter(id=self.event.target.id).first()
            if not command or not command.note:
                return []
            return [ReloadNoteActionButtonsEffect(id=str(command.note.id)).apply()]
    class ReloadFooterOnNoteStateChange(BaseHandler):
        """Reload the footer whenever the note transitions to a new state."""
        RESPONDS_TO = EventType.Name(EventType.NOTE_STATE_CHANGE_EVENT_CREATED)
        def compute(self) -> list[Effect]:
            note_id = self.event.context.get("note_id")
            if not note_id:
                return []
            return [ReloadNoteActionButtonsEffect(id=note_id).apply()]
    ```
##  Reference plugin 
A complete, working plugin that ties these patterns together is available as the [**note-lifecycle-example**](https://github.com/Medical-Software-Foundation/canvas/tree/main/extensions/note-lifecycle-example) plugin. It demonstrates:
  - a full set of state-responsive footer buttons (Lock, Sign, Unlock, Push charges, Check in, No show, Cancel, Restore, Delete, Discharge), each appearing only when its transition is valid from the note's current state — Lock and Sign built on `LockNoteActionButton` and `SignNoteActionButton`, the rest on `NoteStateActionButton`;
  - a `HideDefaultStateButtons` handler that hides Canvas's native footer buttons so the plugin's buttons replace them;
  - `ReloadFooterOnCommandCommit` and `ReloadFooterOnNoteStateChange` handlers that keep the visible button set in sync as the note evolves.
Use it as a starting point for your own footer.
----- END PAGE https://docs.canvasmedical.com/sdk/handlers-action-buttons/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/handlers-applications/
Applications are accessible in the app drawer and launch your content when clicked. Applications can be patient specific, or global.
##  Implementing an Application 
To add an application, your handler class should inherit from the `Application` class.
Your class must implement the `on_open()` method. In most cases, you will return a `LaunchModalEffect`, with either a URL you wish to iframe into the Canvas UI or HTML to be rendered in that iframe directly, make sure to set a `title` so users can easily recognize the application when it's minimized. You can return a single `Effect` or a list of `Effect`s from the `on_open()` method.
You can also optionally implement the `on_context_change()` method to handle context changes within the application. This method is automatically triggered when users navigate to different URLs within Canvas, allowing your application to react to contextual changes with rich information about the current page.
Context change events are currently supported for revenue workflows and include:
  - **URL information** : The current page URL that triggered the context change
  - **Patient data** : Patient information when applicable
  - **Resource-specific context** : Additional context based on the specific page: 
    - `/revenue/claims/<id>` \- Includes claim data with externally exposable ID
    - `/revenue/queues/<id>` \- Includes queue data with database ID
    - `/revenue` \- Base revenue page with no additional context
This method can return an `Effect` or list of `Effect`s to perform actions when the application's context changes, or `None` if no action is needed. When `None` is returned, no effect will be added to the execution queue.
Here is an example of an implemented application class:
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.launch_modal import LaunchModalEffect
    from canvas_sdk.handlers.application import Application
    class IFrameApp(Application):
        def on_open(self) -> Effect | list[Effect]:
            return LaunchModalEffect(url=f"https://www.your-iframe-app.com",
                target=LaunchModalEffect.TargetType.RIGHT_CHART_PANE, title="Your Iframe App").apply()
        def on_context_change(self) -> Effect | list[Effect] | None:
            # Access the current URL that triggered the context change
            current_url = self.event.context.get("url", "")
            # Handle claim-specific context
            if claim := self.event.context.get("claim"):
                claim_id = claim["id"]
                return LaunchModalEffect(
                    url=f"https://www.your-iframe-app.com?claim_id={claim_id}&source_url={current_url}",
                    target=LaunchModalEffect.TargetType.RIGHT_CHART_PANE,
                    title=f"Your Iframe App - Claim {claim_id}"
                ).apply()
            # Handle queue-specific context
            if queue := self.event.context.get("claim_queue"):
                queue_id = queue["dbid"]
                return LaunchModalEffect(
                    url=f"https://www.your-iframe-app.com?queue_id={queue_id}&source_url={current_url}",
                    target=LaunchModalEffect.TargetType.RIGHT_CHART_PANE,
                    title=f"Your Iframe App - Queue {queue_id}"
                ).apply()
            # Handle general revenue page context
            if current_url.startswith("/revenue"):
                return LaunchModalEffect(
                    url=f"https://www.your-iframe-app.com?page=revenue&source_url={current_url}",
                    target=LaunchModalEffect.TargetType.RIGHT_CHART_PANE,
                    title="Your Iframe App - Revenue"
                ).apply()
            # Return None when no relevant context - this will result in an empty effect list
            return None
    ```
##  Context Change Events 
Context change events are automatically triggered when users navigate between different URLs within Canvas. This feature allows your applications to react dynamically to the user's current context, providing relevant information and functionality based on where they are in the system.
###  Event Triggers 
Context change events are currently supported for revenue workflows and are triggered when:
  - A user navigates to a different URL within Canvas
  - The application is already open and running
  - The new URL is within the `/revenue` namespace
###  Context Data Structure 
When a context change event occurs, your `on_context_change()` method receives contextual information through `self.event.context`:
    ```python
    {
        "url": "/revenue/claims/123",           # Current URL that triggered the event
        "patient": {"id": "patient_key"},       # Patient information (when applicable)
        "user": {...},                          # User information
        "claim": {"id": "external_claim_id"},   # Claim context (for /revenue/claims/<id>)
        "claim_queue": {"dbid": "queue_id"}     # Queue context (for /revenue/queues/<id>)
    }
    ```
###  Supported URL Patterns 
URL Pattern | Context Provided | Description  
---|---|---  
`/revenue` | Base context only | General revenue page  
`/revenue/claims/<id>` | `claim` object with externally exposable ID | Specific claim details page  
`/revenue/queues/<id>` | `claim_queue` object with database ID | Specific queue management page  
###  Best Practices 
  1. **Always check for context existence** : Use safe dictionary access patterns to avoid KeyErrors
  2. **Handle multiple context types** : Your application may receive different types of context based on the URL
  3. **Return None appropriately** : When no relevant action is needed, return None to avoid unnecessary effects
###  Advanced Example 
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.launch_modal import LaunchModalEffect
    from canvas_sdk.handlers.application import Application
    class AdvancedRevenueApp(Application):
        def on_open(self) -> Effect | list[Effect]:
            return LaunchModalEffect(
                url="https://www.your-app.com/dashboard",
                target=LaunchModalEffect.TargetType.RIGHT_CHART_PANE,
                title="Revenue Analytics"
            ).apply()
        def on_context_change(self) -> Effect | list[Effect] | None:
            current_url = self.event.context.get("url", "")
            patient = self.event.context.get("patient", {})
            user = self.event.context.get("user", {})
            # Build base parameters
            params = {
                "source_url": current_url,
                "user_id": user.get("id", ""),
                "patient_id": patient.get("id", "")
            }
            # Handle specific contexts
            if claim := self.event.context.get("claim"):
                params["claim_id"] = claim["id"]
                params["view"] = "claim_details"
                title = f"Revenue Analytics - Claim {claim['id']}"
            elif queue := self.event.context.get("claim_queue"):
                params["queue_id"] = queue["dbid"]
                params["view"] = "queue_management"
                title = f"Revenue Analytics - Queue {queue['dbid']}"
            elif current_url.startswith("/revenue"):
                params["view"] = "revenue_overview"
                title = "Revenue Analytics - Overview"
            else:
                # No relevant context for this application
                return None
            # Build query string
            query_string = "&".join(f"{k}={v}" for k, v in params.items() if v)
            return LaunchModalEffect(
                url=f"https://www.your-app.com/revenue?{query_string}",
                target=LaunchModalEffect.TargetType.RIGHT_CHART_PANE,
                title=title
            ).apply()
    ```
In addition, your `CANVAS_MANIFEST.json` file must provide some information about your application. You reference your class in the "applications" section of the components so your application is registered in the app drawer on plugin installation.
This is also where you can define the title and icon that displays your app in the app drawer. The icon will be rendered at 48px by 48px, so should be square and simple enough to not lose detail at that size.
##  Application Scopes 
The `scope` attribute determines where your application is visible within Canvas. The following scopes are available:
Scope | Description  
---|---  
`patient_specific` | Visible only within a patient's chart in the app drawer  
`global` | Visible outside of patient charts in the app drawer  
`full_chart` | Displayed as a tab in the patient chart navigation menu alongside Chart and Profile  
`provider_menu_item` | Displayed as a menu item in the provider menu  
`portal_menu_item` | Displayed as a menu item in the patient portal  
`provider_companion` | Visible on the [Provider Companion](/sdk/companion/) main page (legacy, use `provider_companion_global` for new apps)  
`provider_companion_global` | In the app launcher on the [Provider Companion](/sdk/companion/) main page  
`provider_companion_patient_specific` | As a tab on a patient's page in the [Provider Companion](/sdk/companion/)  
`provider_companion_note_specific` | As a tab within an opened note in the [Provider Companion](/sdk/companion/)  
###  Full Chart Scope 
Applications with the `full_chart` scope appear as navigation tabs at the top of the patient chart, alongside the default "Chart" and "Profile" tabs. This is ideal for building comprehensive patient-level views or dashboards.
    ```json
    {
      "class": "my_plugin.apps.analytics:PatientAnalytics",
      "name": "Analytics",
      "description": "Patient analytics dashboard",
      "icon": "/assets/analytics-icon.png",
      "scope": "full_chart"
    }
    ```
##  Provider Companion Applications 
Provider companion applications run in the Canvas provider companion — a mobile-optimized, provider-facing surface. They use the `Application` handler with one of three companion scopes (`provider_companion_global`, `provider_companion_patient_specific`, `provider_companion_note_specific`) declared in the manifest. The legacy `provider_companion` scope continues to work and is treated the same as `provider_companion_global`.
See [Provider Companion](/sdk/companion/) for the full guide — scope-by-scope examples, event context, code sharing across scopes, originating commands from a note, modal dismissal, and mobile UX guidance.
##  Embedded Applications 
Note Applications (tabs inside a note), Scheduling Applications (which replace the built-in scheduling modal), and Docked Applications (a persistent pane pinned to a window edge) are **embedded applications** — handler-based applications that render inside a specific Canvas surface rather than appearing in the app drawer. They are declared under `handlers` (not `applications`), take no `scope` or `icon`, and create no application record.
See [Embedded Applications](/sdk/handlers-embedded-applications/) for the full guide.
##  Panel Display 
If you want to increase your application's visibility and display it alongside other panel buttons (instead of in the applications drawer), you can add the `show_in_panel` attribute. If you've added more than one application to that panel, you can set their priorities using the `panel_priority` attribute.
For security reasons you also need to specify the domains that will be loaded within the iframe, or they will not be rendered. For more info on the format of the `url_permissions` field, check the [Additional Configuration](/sdk/layout-effect/#additional-configuration) for `LaunchModalEffect`.
Here's what your `CANVAS_MANIFEST.json` might look like:
    ```json
    {
      "sdk_version": "0.1.4",
      "plugin_version": "0.0.1",
      "name": "my_application",
      "description": "This is a very nice application",
      "url_permissions": [
        {
          "url": "https://example.com/",
          "permissions": ["ALLOW_SAME_ORIGIN", "MICROPHONE", "SCRIPTS", "CAMERA", "CLIPBOARD_READ", "CLIPBOARD_WRITE"]
        }
      ],
      "components": {
        "handlers": [],
        "applications": [
          {
            "class": "my_application.apps.iframe:IFrameApp",
            "name": "My Application",
            "description": "Test App for patients",
            "icon": "/assets/cappuccino.png",
            "scope": "patient_specific",
            "show_in_panel": true,
            "panel_priority": 100
          }
        ],
        "commands": [],
        "content": [],
        "effects": [],
        "views": []
      },
      "variables": [],
      "tags": {},
      "references": [],
      "license": "",
      "diagram": false,
      "readme": "./README.md"
    }
    ```
##  Opening an Application on Load 
You can configure an application to open **automatically** , without the user clicking its icon, by enabling the **Open on load** setting for that application in your instance settings.
To enable it, go to the Plugins_IO > Applications section of your instance settings (`/admin/plugin_io/application/`), open the application you want, check **Open on load** , and save. If you don't have access to this setting, reach out to Canvas Support.
Behavior depends on the application's scope:
Scope | When it opens  
---|---  
`global` | Automatically when the app shell first loads.  
`patient_specific` | Automatically when a patient chart is opened.  
This is an instance-level setting configured per application in your instance settings. It is **not** part of `CANVAS_MANIFEST.json`, so the value you set is preserved when the plugin is reinstalled or updated.
> **Warning:** **Enable Open on load for at most one application per scope.** There is no priority or ordering logic for this setting, and no constraint preventing multiple applications in the same scope from being flagged. If more than one application in the same scope (for example, two `patient_specific` apps) has Open on load enabled, all of them will attempt to open, resulting in unpredictable behavior. Make sure only one application per scope is set to open on load. 
> **Note:** This is distinct from a Note Application's [`open_by_default()`](/sdk/handlers-embedded-applications/#opening-by-default), which controls which **tab** is active when a note is viewed. **Open on load** controls whether a `global` or `patient_specific` application opens automatically on app/chart load.
##  Notification Badges 
You can display a notification badge — a small count — on a `global`, `patient_specific`, or `provider_menu_item` application: on the icon in the app drawer or panel (`global` / `patient_specific`, the latter when the application sets `show_in_panel`), or next to the label in the provider menu (`provider_menu_item`). A badge is useful for surfacing how many items are waiting for attention, such as unread messages or open tasks. Applications in other scopes (`full_chart`, `portal_menu_item`, and the Provider Companion scopes) do not display badges.
###  Initial count on load 
Override `compute_notification_badge()` on your `Application` handler to provide the count shown when Canvas loads applications. Return an integer to show a badge, or `None` (the default) to show no badge. A count of `0` shows no badge.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.launch_modal import LaunchModalEffect
    from canvas_sdk.handlers.application import Application
    from canvas_sdk.v1.data.task import Task, TaskStatus
    class InboxApp(Application):
        def on_open(self) -> Effect | list[Effect]:
            return LaunchModalEffect(
                url="https://www.your-app.com/inbox",
                title="Inbox",
            ).apply()
        def compute_notification_badge(self) -> int | None:
            """Return the badge count shown on the icon when applications load."""
            staff_id = self.event.context.get("staff", {}).get("id")
            if not staff_id:
                return None
            return Task.objects.filter(assignee__id=staff_id, status=TaskStatus.OPEN).count()
    ```
When the application is rendered on a patient chart (`patient_specific` scope), the event context also carries the patient, so you can compute a count specific to the staff member _and_ the patient they are viewing:
    ```python
    from canvas_sdk.v1.data.task import Task, TaskStatus
    def compute_notification_badge(self) -> int | None:
        staff_id = self.event.context.get("staff", {}).get("id")
        patient_id = self.event.context.get("patient", {}).get("id")
        if not (staff_id and patient_id):
            return None
        return Task.objects.filter(
            assignee__id=staff_id, patient__id=patient_id, status=TaskStatus.OPEN
        ).count()
    ```
The badge event context contains:
Key | Description  
---|---  
`staff` | A dict with the staff `id` and `type` (present for staff-facing apps).  
`patient` | A dict with the patient `id` and `type` (present on a patient chart).  
> **Note:** Note Applications (`NoteApplication`) do not support notification badges.
###  Live updates 
To change the count after load — for example, in response to a new task or message — emit an `ApplicationNotificationBadge` effect from any event handler. The badge updates in real time without the user reloading the page. See the [Application Notification Badge](/sdk/effect-application-notification-badge/) effect for details.
----- END PAGE https://docs.canvasmedical.com/sdk/handlers-applications/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/handlers-basehandler/
The `BaseHandler` is the simplest of all handlers. Specify which events you are interested in, then provide the code to execute when one of those events is emitted. All the [handlers](/sdk/handlers/) inherit from `BaseHandler`, and many of yours will too.
##  Handling Events With `BaseHandler`
To create a class that responds to one or more events, inherit from `BaseHandler`, set the `RESPONDS_TO` constant, and implement the `compute()` method.
    ```python
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers.base import BaseHandler
    class MyEventHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.TASK_CREATED)
        def compute(self):
            # Your code goes here!
            return []
    ```
You can respond to one event, or several. To respond to multiple events, set `RESPONDS_TO` to a list of [event types](/sdk/events/).
    ```python
    # Respond when tasks are created:
    RESPONDS_TO = EventType.Name(EventType.TASK_CREATED)
    # Respond when tasks are created OR updated:
    RESPONDS_TO = [
        EventType.Name(EventType.TASK_CREATED),
        EventType.Name(EventType.TASK_UPDATED),
    ]
    ```
The `compute()` method must return a list of [Effects](/sdk/effects/). That list can be empty, of course. You have access to event information with `self.event`, `self.target`, and`self.context`, as well as configuration information for your plugin with `self.secrets` and for the running instance with `self.environment`. You can use our [Data Module](/sdk/data/) to retrieve additional information at runtime.
----- END PAGE https://docs.canvasmedical.com/sdk/handlers-basehandler/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/handlers-crontask/
You can write a handler that subclasses `CronTask` to execute code on a schedule. You might use this to automate reporting on task performance (every hour, send the number of tasks created during the previous hour, and the number of tasks completed during the previous hour), to send birthday wishes to your patients (at 7am every morning, find patients born on this day and send them an email wishing them a happy birthday), or to send customized appointment reminders (every hour, find appointments that start on this hour the following day and send the patients of those appointments a message that says "Dr. X is looking forward to seeing you at (location) at (time)").
##  Example 
This example `CronTask` shows an extremely basic example that just logs the time. You can see the critical pieces here:
  - Subclass `CronTask`
  - Set your `SCHEDULE` with a cron string
  - Implement an `execute` method with the code you want to schedule to run, returning a list containing any effects you want to return, or an empty list if you do not wish to return any effects.
    ```python
    from canvas_sdk.handlers.cron_task import CronTask
    from canvas_sdk.effects import Effect
    from logger import log
    class LogTheTime(CronTask):
        # A cron string.
        #           ┌───────────── minute (0 - 59)
        #           │ ┌───────────── hour (0 - 23)
        #           │ │ ┌───────────── day of the month (1 - 31)
        #           │ │ │ ┌───────────── month (1 - 12)
        #           │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday)
        #           │ │ │ │ │
        #           │ │ │ │ │
        #           │ │ │ │ │
        SCHEDULE = "* * * * *"  # Run every minute, which is the most frequently something can run.
        def execute(self) -> list[Effect]:
            # The current timestamp can be found as an iso8601 string in
            # `self.target`
            log.info(f"The current time is {self.target}")
            # We don't need to return any effects
            return []
    ```
###  Output 
Here's what this scheduled task would output in the logs:
`INFO 2024-07-18 18:47:00,000 The current time is 2024-07-18T18:47:00.000000+00:00`
----- END PAGE https://docs.canvasmedical.com/sdk/handlers-crontask/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/handlers-embedded-applications/
Embedded applications render **inside a specific Canvas surface** (a tab within a note, the scheduling modal, or a pane pinned to a window edge) rather than as an icon in the app drawer. They are ordinary [handlers](/sdk/handlers-basehandler/): you subclass a base class, register it under `handlers` in your `CANVAS_MANIFEST.json`, and Canvas renders it in the appropriate surface.
There are three kinds:
Base class | Surface  
---|---  
`NoteApplication` | A tab within a patient's note  
`SchedulingApplication` | Replaces the built-in scheduling modal at every entry point  
`DockedApplication` | A persistent pane pinned to a window edge, always visible  
##  How embedded applications work 
Embedded applications are [handlers](/sdk/handlers-basehandler/). You build one by subclassing `NoteApplication`, `SchedulingApplication`, or `DockedApplication` and registering it under `handlers` in your `CANVAS_MANIFEST.json` — everything else is inherited from that parent class.
Because the parent class defines the behavior, there's very little to configure:
  - The **surface** comes from the class you inherit — `NoteApplication` renders as a tab in a note, `SchedulingApplication` replaces the scheduling modal, and `DockedApplication` pins a persistent pane to a window edge. You don't set a `scope` or an `icon`.
  - Canvas renders Note and Scheduling Applications **on demand** : when a note opens or a scheduling action is triggered, Canvas asks which embedded application is installed for that surface, then renders what your handler returns. A Docked Application is the exception: it stays mounted at all times instead of rendering on demand. None of the three are persisted as drawer applications, so they don't appear in the app drawer or under Plugins_IO > Applications.
  - If no embedded application is installed for a surface, Canvas falls back to its built-in behavior — an unmodified note, or the built-in scheduling modal.
Since the surface and scope are inherited from the parent class, register these under `handlers` rather than `applications`.
##  Note Applications 
Note Applications appear as tabs within a patient's note, allowing you to embed custom interfaces directly in the clinical documentation workflow.
###  Implementing a Note Application 
To create a Note Application, your handler class should inherit from `NoteApplication` and define the following class attributes:
Attribute | Description  
---|---  
`NAME` | (Required) The display title shown on the tab (supports emojis)  
`IDENTIFIER` | (Required) A unique key for the application (recommended format: `plugin_name__app_name`)  
`PRIORITY` | (Optional) Controls tab order — lower values appear first. Defaults to `0`  
> **Tip:** If your Note Application is named "Note", it may cause confusion with the built-in Note tab. Users can rename the built-in tab by updating the Constance Config setting `NOTE_BODY_TAB_LABEL` in your instance Settings, to avoid duplication.
Your class must implement the `on_open()` method, which is called when the user clicks on the tab. This method should return an `Effect` or list of `Effect`s, typically a `LaunchModalEffect` with `target` set to `LaunchModalEffect.TargetType.NOTE`
> **⚠️ Important** If you have an existing plugin that overrides `handle()`, it will continue to work. However, `handle()` is deprecated — migrate to `on_open()` at your earliest convenience.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.launch_modal import LaunchModalEffect
    from canvas_sdk.handlers.application import NoteApplication
    class PatientIntakeApp(NoteApplication):
        """Note application for patient intake workflow."""
        NAME = "📋 Patient Intake"
        IDENTIFIER = "my_plugin__patient_intake"
        def on_open(self) -> Effect | list[Effect]:
            """Launch the intake form when the tab is clicked."""
            note_id = self.event.context.get("note_id")
            patient_id = self.event.context.get("patient", {}).get("id")
            return LaunchModalEffect(
                target=LaunchModalEffect.TargetType.NOTE,
                content="<html>Your form HTML here</html>",
                title="Patient Intake Form"
            ).apply()
    ```
![note applications](/assets/images/note-application-tabs.png)
###  Manifest Configuration 
Register your Note Application under the `handlers` section of your `CANVAS_MANIFEST.json`. There is no `scope` or `icon` — the note tab is driven by the `NoteApplication` base class and the `NAME`/`IDENTIFIER` class attributes.
    ```json
    {
      "components": {
        "handlers": [
          {
            "class": "my_plugin.apps.intake:PatientIntakeApp",
            "description": "In-note patient intake tab."
          }
        ]
      }
    }
    ```
###  Context and Event Data 
Both `on_open()` and `handle()` have access to context data through `self.event.context`:
Key | Description  
---|---  
`note_id` | The database ID of the current note  
`note` | A dict containing the note's external `id` (UUID)  
`patient` | A dict containing the patient's `id`  
`user` | Information about the current user  
####  `on_open()` — recommended 
When using `on_open()`, the patient is available through `self.event.context`:
    ```python
    from canvas_sdk.effects import Effect
    def on_open(self) -> Effect | list[Effect]:
        note_id = self.event.context.get("note_id")
        patient_id = self.event.context.get("patient", {}).get("id")
        ...
    ```
`self.event.target.id` contains the application identifier used internally for routing, not the patient.
####  `handle()` — deprecated 
When using the deprecated `handle()`, `self.event.target.id` is automatically set to the patient UUID before `handle()` is called, preserving the original behavior that old plugins relied on:
    ```python
    from canvas_sdk.effects import Effect
    def handle(self) -> list[Effect]:
        patient_id = self.event.target.id  # backfilled from patient context
        ...
    ```
> **Note:** This backfilling only happens when `handle()` is called. Plugins that override `on_open()` directly should read the patient from `self.event.context` as shown above.
Property | `on_open()` | `handle()` (deprecated)  
---|---|---  
`self.event.target.id` | Application identifier (for routing) | Patient UUID (backfilled)  
`self.event.context["patient"]["id"]` | Patient UUID | Patient UUID  
`self.event.actor` | Authenticated user | Authenticated user  
###  Controlling Visibility 
You can control when your Note Application tab is visible by overriding the `visible()` method. This method has access to the same context and event data as `on_open()`:
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.launch_modal import LaunchModalEffect
    from canvas_sdk.handlers.application import NoteApplication
    class ConditionalIntakeApp(NoteApplication):
        NAME = "📋 Intake"
        IDENTIFIER = "my_plugin__conditional_intake"
        def visible(self) -> bool:
            """Only show for specific conditions."""
            # Add your visibility logic here
            return True
        def on_open(self) -> Effect | list[Effect]:
            return LaunchModalEffect(
                target=LaunchModalEffect.TargetType.NOTE,
                content="<html>Form content</html>",
                title="Intake"
            ).apply()
    ```
###  Opening by Default 
You can make a Note Application tab open automatically when a note is first viewed by overriding `open_by_default()`. If multiple applications return `True`, the first one (by priority order) will be opened.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.launch_modal import LaunchModalEffect
    from canvas_sdk.handlers.application import NoteApplication
    class AutoOpenApp(NoteApplication):
        NAME = "📋 Intake"
        IDENTIFIER = "my_plugin__auto_open_intake"
        def open_by_default(self) -> bool:
            """Open automatically when the note is viewed."""
            return True
        def on_open(self) -> Effect | list[Effect]:
            return LaunchModalEffect(
                target=LaunchModalEffect.TargetType.NOTE,
                content="<html>Form content</html>",
                title="Intake"
            ).apply()
    ```
###  Tab Ordering 
You can control the order in which Note Application tabs appear by setting the `PRIORITY` class attribute. Tabs are sorted in ascending order, so lower values appear first. The default is `0`.
    ```python
    from canvas_sdk.handlers.application import NoteApplication
    class HighPriorityApp(NoteApplication):
        NAME = "First Tab"
        IDENTIFIER = "my_plugin__first"
        PRIORITY = 1
    class LowPriorityApp(NoteApplication):
        NAME = "Second Tab"
        IDENTIFIER = "my_plugin__second"
        PRIORITY = 10
    ```
> **Note:** Note Applications do not support [notification badges](/sdk/handlers-applications/#notification-badges).
##  Scheduling Applications 
Scheduling Applications replace the built-in scheduling modal throughout Canvas. When you install a plugin with a scheduling application, it takes over all scheduling entry points: the schedule page, patient chart, calendar drag-and-drop, calendar reschedule, and note reschedule flows.
###  Implementing a Scheduling Application 
To create a Scheduling Application, your handler class should inherit from `SchedulingApplication` and define two required class attributes:
Attribute | Description  
---|---  
`NAME` | The display title shown in the modal  
`IDENTIFIER` | A unique key for the application (recommended format: `plugin_name__app_name`)  
Your class must implement the `on_open()` method, which is called when a scheduling action is triggered. This method should return an `Effect` or list of `Effect`s, typically a `LaunchModalEffect`.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.launch_modal import LaunchModalEffect
    from canvas_sdk.handlers.application import SchedulingApplication
    class CustomScheduler(SchedulingApplication):
        """Scheduling application for custom appointment booking."""
        NAME = "Schedule Appointment"
        IDENTIFIER = "my_plugin__scheduler"
        def on_open(self) -> Effect | list[Effect]:
            """Launch the scheduling form when triggered."""
            patient = self.event.context.get("patient", {})
            provider = self.event.context.get("provider", {})
            start = self.event.context.get("start", "")
            mode = self.event.context.get("mode", "schedule")
            return LaunchModalEffect(
                url=f"https://scheduler.example.com/book?patient={patient.get('id', '')}&provider={provider.get('id', '')}&start={start}&mode={mode}",
                title="Schedule Appointment"
            ).apply()
    ```
###  Context Data 
When `on_open()` is called, scheduling context is available through `self.event.context`. The available keys depend on which entry point triggered the scheduling action.
####  Entity Objects 
Entities are delivered as `{"id": <external id>}` objects, resolvable with the conventional `.objects.get(id=...)`:
Field | Resolves To | Value | Available From  
---|---|---|---  
`patient` | [Patient](/sdk/data-patient/#patient) | `{"id": <patient id>}` | Patient chart, reschedule flows  
`provider` | [Staff](/sdk/data-staff/#staff) | `{"id": <staff id>}` | Calendar, patient chart  
`location` | [PracticeLocation](/sdk/data-practicelocation/#practicelocation) | `{"id": <practice location id>}` | Current location context  
`appointment` | [Appointment](/sdk/data-appointment/#appointment) | `{"id": <appointment id>}` | Reschedule flows  
`note` | [Note](/sdk/data-note/#note) | `{"id": <note id>}` | Note reschedule flow  
####  Scalar Values 
Key | Description  
---|---  
`start` | ISO-8601 datetime of the selected slot (all entry points)  
`end` | ISO-8601 datetime for the slot end (calendar drag-and-drop only)  
`duration` | Slot length in minutes (reschedule flows only). Either `end` or `duration` is present, never both  
`mode` | One of `schedule`, `reschedule`, or `followup`  
`origin` | The launching surface: `schedule_page`, `patient_chart`, `calendar`, `calendar_reschedule`, or `note_reschedule`  
When `end` is not provided, derive it from `start + duration`.
####  Origins 
`origin` tells you which surface launched the scheduling action, which in turn determines the `mode` and whether the slot length arrives as `end` or `duration`:
`origin` | Launching surface | `mode` | Slot length  
---|---|---|---  
`schedule_page` | **New appointment** from the schedule page (no patient context) | `schedule` or `followup` | neither (`start` only)  
`patient_chart` | **New appointment** from a patient's chart | `schedule` or `followup` | neither (`start` only)  
`calendar` | Drag-and-drop on the calendar to create a slot | `schedule` | `end`  
`calendar_reschedule` | Rescheduling an existing appointment from the calendar | `reschedule` | `duration`  
`note_reschedule` | Rescheduling an appointment from within a note | `reschedule` | `duration`  
Which entities accompany each origin is shown in the Entity Objects table's "Available From" column above — for example, `patient_chart` and the reschedule flows include a `patient`, while `schedule_page` and `calendar` do not.
###  Manifest Configuration 
Register your Scheduling Application under the `handlers` section of your `CANVAS_MANIFEST.json`. There is **no** `scope` or `icon` — inheriting from `SchedulingApplication` is what tells Canvas to use it as the scheduling-modal override.
    ```json
    {
      "components": {
        "handlers": [
          {
            "class": "my_plugin.apps.scheduler:CustomScheduler",
            "description": "Custom appointment scheduling that overrides the built-in modal."
          }
        ]
      }
    }
    ```
When installed, this application replaces the built-in scheduling modal. If no scheduling application is installed, the existing built-in modal continues to work unchanged.
##  Docked Applications 
A Docked Application mounts as a persistent **docked pane** pinned to a window edge. It stays in place as the user moves between pages, including between a patient chart and global pages, rather than opening fresh each time.
Reach for a docked pane when a surface needs to follow the user instead of being opened and re-opened: a telephony or messaging bar that has to survive navigation mid-call, a live worklist the user works through while moving between charts, or an ambient scribe that keeps recording as the clinician moves around a note. Because the pane stays mounted, whatever state it holds survives that navigation, whether that's a scroll position, a half-filled form, or an open connection. A modal or an overlay cannot do this, since both are torn down when the page changes.
###  Implementing a Docked Application 
A Docked Application is a handler that inherits from `DockedApplication`, declares the pane's placement as class attributes, and implements `on_open()` to mount the pane's content:
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.launch_modal import LaunchModalEffect
    from canvas_sdk.handlers.application import DockedApplication, DockEdge
    from canvas_sdk.templates import render_to_string
    from canvas_sdk.v1.data.task import Task, TaskStatus
    class TaskDock(DockedApplication):
        """Docked application that keeps the signed-in user's open tasks on screen."""
        NAME = "My Tasks"
        IDENTIFIER = "my_plugin__task_dock"
        DOCK_EDGE = DockEdge.RIGHT
        DOCK_SIZE = "320px"
        def on_open(self) -> Effect | list[Effect]:
            """Mount the docked pane's content."""
            user_id = self.event.context.get("user", {}).get("id")
            tasks = Task.objects.filter(assignee__id=user_id, status=TaskStatus.OPEN).order_by("due")
            return LaunchModalEffect(
                target=LaunchModalEffect.TargetType.DOCKED_PANE,
                content=render_to_string("templates/task_dock.html", {"tasks": tasks}),
                title="My Tasks",
            ).apply()
    ```
The pane's markup lives in a Django template in your plugin rather than in a Python string, rendered by [`render_to_string`](/sdk/layout-effect/#custom-html-and-django-templates). Here that template is `templates/task_dock.html`:
    ```html
    <!DOCTYPE html>
    <html>
      <head>
        <title>My Tasks</title>
      </head>
      <body>
        <h1>My Tasks</h1>
        <ul>
          {% for task in tasks %}
            <li>{{ task.title }} (due {{ task.due }})</li>
          {% endfor %}
        </ul>
      </body>
    </html>
    ```
`on_open()` returns a [`LaunchModalEffect`](/sdk/layout-effect/#modals) with `target` set to `LaunchModalEffect.TargetType.DOCKED_PANE`. That target is what mounts the effect's rendered `content` (or a `url`) in the pane rather than in a modal. Canvas draws no chrome around a docked pane, so `title` is never displayed: it becomes the pane's accessible name for screen readers.
There is no app drawer entry that opens a docked pane and no Canvas-provided control that closes or minimizes it. For the staff who get one, the pane is simply on screen for the whole session, and which staff those are is the plugin's decision: see Controlling who gets a pane. The pane's own content can remove itself at runtime, which is how a plugin offers its own collapse or close control. See Sizing, Resizing, and Collapsing.
####  Class attributes 
Attribute | Required | Description  
---|---|---  
`NAME` | Required | The display title for the pane  
`IDENTIFIER` | Optional | A unique key for the application. When omitted, the identifier defaults to one derived automatically from the class's module and name. Set it explicitly in the recommended `plugin_name__app_name` format to give the application a stable, readable identifier.  
`DOCK_EDGE` | Required | Which window edge to pin the pane to, given as a `DockEdge` value  
`DOCK_SIZE` | Required | The pane's initial size and the initial ceiling for the plugin's own resize requests, as a CSS length string (for example, `320px`). See Sizing, Resizing, and Collapsing.  
`PRIORITY` | Optional | An integer controlling stacking order when multiple panes share an edge — lower values sit nearer the window edge. Defaults to `0`. See Multiple Docked Panes.  
####  DockEdge 
`DockEdge` is an enum of the four window edges a pane can be pinned to.
Name | Value | Edge to pin to  
---|---|---  
`LEFT` | `left` | Left edge of the window  
`RIGHT` | `right` | Right edge of the window  
`TOP` | `top` | Top edge of the window  
`BOTTOM` | `bottom` | Bottom edge of the window  
####  Controlling who gets a pane 
A docked pane is not all-or-nothing across an instance. Override `visible()` to decide, per staff member, whether the pane exists for them at all:
    ```python
    def visible(self) -> bool:
        """Only dock the pane for the care coordination team."""
        staff_id = self.event.context.get("user", {}).get("id")
        return Staff.objects.filter(id=staff_id, teams__name="Care Coordination").exists()
    ```
Returning `False` means that user gets no pane: Canvas never calls `on_open()` for them and sends them no context changes. `visible()` defaults to `True`, so a Docked Application that does not override it docks for everyone.
Two things to know about when this runs. Canvas asks for docked applications once as the EHR shell loads, so `visible()` is evaluated then and not again as the user navigates inside the shell. A change in the answer therefore takes effect on that user's next full page load. And the context `visible()` receives holds only `scope` and `user`, with no `patient` or `note`, because the question being asked is which panes this session gets rather than what is on screen. Gate on the staff member, or on anything you can look up from them, rather than on what they are currently viewing.
Do not use `on_open()` for this. Returning no docked-pane effect from `on_open()` also leaves the pane unmounted, but panes are mounted once per session and never retried, so the pane stays gone for the rest of the session with no way back.
###  Pane Context and Navigation 
Like other embedded applications, a Docked Application reads request context from `self.event.context`. It receives that context twice over: once when the pane first mounts, through `on_open()`, and again on each navigation, through `on_context_change()`. Both entry points get the same three keys:
Key | Value | Description  
---|---|---  
`url` | `str` | The path of the Canvas page the user is on. Always one of the pages a pane sees.  
`user` | `{"id": str, "type": str}` | The signed-in user, always present. `id` is the [Staff](/sdk/data-staff/#staff) id; `type` is the name of the person record behind the login, which is `Staff` in the EHR.  
`patient` | `{"id": str}` | The patient whose chart is open. Present only on a `/patient/<patient id>` path and absent entirely elsewhere, so read it as `self.event.context.get("patient", {})`.  
On a patient chart the whole context looks like this:
    ```python
    {
        "url": "/patient/b80b1cdc2e6a4aca90ccebc02e683f35",
        "user": {"id": "5eede137ecfe4124b8b773040e33be14", "type": "Staff"},
        "patient": {"id": "b80b1cdc2e6a4aca90ccebc02e683f35"},
    }
    ```
On the schedule, where no patient is in the path, the `patient` key is simply not there:
    ```python
    {
        "url": "/schedule",
        "user": {"id": "5eede137ecfe4124b8b773040e33be14", "type": "Staff"},
    }
    ```
####  `on_open()`
Fires once, when the pane mounts, and returns the effect that gives the pane its content:
    ```python
    def on_open(self) -> Effect | list[Effect]:
        url = self.event.context.get("url")
        user_id = self.event.context.get("user", {}).get("id")
        patient_id = self.event.context.get("patient", {}).get("id")
        ...
    ```
####  `on_context_change()`
Fires on each navigation after that, with the new `url` and the `patient` derived from the new path. This is how a pane stays context-aware as the user moves around Canvas.
It defaults to a no-op, so a pane that does not override it keeps whatever it last rendered. Override it to return the plugin's own content or hosted URL, rebuilt from the new context, such as a hosted URL that carries the new patient id:
    ```python
    def on_context_change(self) -> Effect | list[Effect]:
        patient_id = self.event.context.get("patient", {}).get("id", "")
        return LaunchModalEffect(
            target=LaunchModalEffect.TargetType.DOCKED_PANE,
            url=f"https://task-dock.example.com/panel?patient={patient_id}",
        ).apply()
    ```
The pane's document reloads only when `on_context_change()` returns a different `url` or `content`. Returning the same `url` or `content` as before leaves the pane's current document in place, preserving its scroll position and state, and so does returning no effect at all (`None` or an empty list). Whether to reload is your plugin's choice.
####  Pages a pane sees 
`url` is the path of the Canvas page around the pane, never the URL loaded inside the pane's own iframe. A pane navigating its own document is invisible to Canvas and produces no context change, and neither does a change to the page URL's hash.
Docked panes mount in the EHR shell, so `url` is always one of the paths that shell routes. Some of these pages are gated by permission or by a feature flag, so which of them a given user reaches will vary:
Path | Page  
---|---  
`/patient/<patient id>` | A patient's chart, including its sub-paths. The only path that carries a `patient` in context  
`/schedule` | The schedule calendar  
`/patients` | The patient list  
`/panel` | A patient panel  
`/populations` | Population health  
`/campaigns` | Campaigns  
`/revenue` | Revenue and claims  
`/data-integration` | Data integration  
`/questionnaire-builder` | The questionnaire builder  
`/application` | A full-page plugin application  
`/403` | Permission denied  
Everything else on the domain sits outside that shell, including `/admin`, `/login`, `/app/...`, `/companion/...` and `/plugin-io/...`. Moving to one of those is a full page load rather than a navigation: the pane is not on screen while the user is there, and returning to the EHR mounts it again from scratch, so `on_open()` fires rather than `on_context_change()`.
The patient portal is outside the shell as well, so a docked pane cannot appear there. A Docked Application is a staff-facing surface only.
Because the pane stays mounted across every navigation inside the shell, it keeps its state there instead of being recreated like a modal or overlay that opens and closes.
###  Multiple Docked Panes 
Docked panes stack rather than being limited to one per edge. Each edge holds up to two panes. When an edge already holds its two panes, an additional pane for that edge is not displayed — it is ignored rather than raising an install-time error.
Panes sharing an edge share a single track, and they split it evenly. On a left or right edge they stack top to bottom, each as wide as the track; on a top or bottom edge they sit side by side, each as tall as it. So two panes on one edge each get half the length of that edge, and the edge itself is only as thick as the larger of their two `DOCK_SIZE` values, since those sizes are alternatives rather than additions.
Which pane comes first is decided by priority: the lower `PRIORITY` value goes first, which means the top of the stack on a left or right edge and the left-hand position on a top or bottom edge. Panes with equal priority are ordered by their identifier. This mirrors the way the `PRIORITY` class attribute orders Note Application tabs. To get a predictable order, set an explicit `PRIORITY` on each pane, and set an explicit `IDENTIFIER` rather than relying on the auto-derived one.
Panes cannot crowd Canvas out of its own window. The left and right panes together take up at most half the window's width, and the top and bottom panes together at most half its height. If the panes on one of those pairs would exceed their half, all of them are scaled down in proportion rather than any one being dropped.
###  Sizing, Resizing, and Collapsing 
`DOCK_SIZE` sets the pane's initial size — its width on the `LEFT` and `RIGHT` edges, its height on the `TOP` and `BOTTOM` edges — and the initial ceiling for the plugin's own resize requests. The user and the plugin resize the pane under different rules.
A user can resize a pane by dragging its edge or with the keyboard arrow keys, using the standard splitter the pane exposes. A user resize can make the pane larger or smaller than `DOCK_SIZE`: it stops at 48px, and at the half-the-window limit described above, but is not otherwise bound by `DOCK_SIZE`. The size a user drags to becomes the pane's new ceiling, replacing `DOCK_SIZE`, and is stored in the browser (`localStorage`) keyed by the pane's identifier, so it survives page reload and navigation. Once a user has resized a pane, that persisted size takes precedence on future loads, so changing `DOCK_SIZE` in a later plugin version does not affect panes a user has already resized, until the stored size is cleared.
There is no host-provided control to close or minimize the pane. A plugin can, however, resize or collapse its own pane from inside its iframe (for example, by collapsing it to a thin rail). A plugin's own resize can shrink the pane freely, down to a thin rail. It cannot grow the pane past the current ceiling: a request at or above the ceiling is clamped to the ceiling rather than applied as given, so a plugin restores the pane to its full (ceiling) size by requesting any value at or above it. A plugin-driven resize is not bound by the 48px floor that applies to user resizing.
A pane is removed only when the plugin's own content requests it, by posting the same `CLOSE_MODAL` message that applications use to dismiss modals. See [Closing Modals from Applications](/sdk/layout-effect/#closing-modals-from-applications) for the full mechanism. Once removed, nothing re-mounts the pane short of a page reload.
A docked pane cannot navigate the host application directly; navigation is issued through a redirect effect. It does not reset the session idle-logout timer.
###  Manifest Configuration 
Register your Docked Application under the `handlers` section of your `CANVAS_MANIFEST.json`. As with Note and Scheduling Applications, there is **no** `scope` or `icon` — inheriting from `DockedApplication` is what tells Canvas to mount it as a docked pane.
    ```json
    {
      "components": {
        "handlers": [
          {
            "class": "my_plugin.apps.task_dock:TaskDock",
            "description": "Open task list docked to the right edge."
          }
        ]
      }
    }
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/handlers-embedded-applications/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/handlers-payment-processors/
The Canvas SDK provides a way to define custom payment processors that enables Canvas to:
  - Handle payment-related workflows within the platform
  - Integrate with third-party payment providers
  - Manage payment methods for patients
Custom payment processors enable flexibility, allowing developers to replace Stripe with other payment providers. Currently, only credit card payment processors are supported.
##  Payment Processor Handler 
All payment processors are implemented as subclasses of the abstract `PaymentProcessor` handler. This defines the base contract for:
  - Selecting a processor
  - Charging payments
  - Listing, adding, and removing payment methods
You can find it in:
    ```python
    from canvas_sdk.handlers.payment_processors.base import PaymentProcessor
    ```
`PaymentProcessor` is an abstract base that wires up event handling but does not define the methods a plugin needs to implement. You should not subclass it directly — instead, extend one of its typed subclasses. Currently the only supported type is `CardPaymentProcessor`, which is where the methods you need to define are declared.
* * *
##  CardPaymentProcessor 
If your custom processor deals with card payments, extend the `CardPaymentProcessor`, which provides additional structure.
    ```python
    from canvas_sdk.handlers.payment_processors.card import CardPaymentProcessor
    ```
In the plugin, this is implemented as:
    ```python
    from canvas_sdk.handlers.payment_processors.card import (
        CardPaymentProcessor,
    )
    class PayTheoryPaymentProcessor(CardPaymentProcessor):
        ...  # Your implementation here
    ```
* * *
##  Defining a `CardPaymentProcessor`
When implementing a `CardPaymentProcessor`, you must define how Canvas should display forms for adding and charging cards, how to process those payments, and how to manage saved payment methods.
In order to implement a `CardPaymentProcessor`, you should override and implement the following methods grouped into three main categories:
  - Displaying forms
  - Charging a card
  - Managing payment methods
###  Displaying Forms 
The forms you return via [`PaymentProcessorForm`](/sdk/payment-processor-effect/#paymentprocessorform) are rendered directly as **inner HTML** within the Canvas application. These forms must define a small contract of JavaScript behaviors to allow Canvas to communicate with them.
These methods define the HTML content that will be rendered inside Canvas to collect card data for payments or adding new cards. They must comply with the Form Workflow.
These forms render wherever Canvas collects a card payment or manages saved payment methods, including:
  - the **provider UI** — the Revenue module and the patient profile payment/insurance area
  - the **Patient Portal** — when a patient makes a payment or manages their saved cards
####  Payment Form 
Triggered when a user selects the credit card payment option. This method should return the HTML form used to collect and tokenize the payment details.
    ```python
    from canvas_sdk.effects.payment_processor import PaymentProcessorForm
    from canvas_sdk.templates import render_to_string
    from canvas_sdk.v1.data import Patient
    def payment_form(self, patient: Patient | None = None) -> PaymentProcessorForm:
        content = render_to_string("templates/payment_form.html")
        return PaymentProcessorForm(intent="pay", content=content)
    ```
* * *
####  Add Card Form 
Triggered when a user initiates the process of adding a new card to a patient's saved payment methods. This method should return the HTML form used to collect and tokenize the new card information.
    ```python
    from canvas_sdk.effects.payment_processor import PaymentProcessorForm
    from canvas_sdk.templates import render_to_string
    from canvas_sdk.v1.data import Patient
    def add_card_form(self, patient: Patient | None = None) -> PaymentProcessorForm:
        content = render_to_string(
            "templates/add_card_form.html",
            {"payor_id": self.api.get_default_payor_id()},
        )
        return PaymentProcessorForm(intent="add_card", content=content)
    ```
* * *
###  Charging a Card 
Handles the actual payment. This method is triggered after tokenization and is responsible for charging the card. It returns a [`CardTransaction`](/sdk/payment-processor-effect/#cardtransaction) effect describing the result of the charge:
    ```python
    from decimal import Decimal
    from typing import Any
    from canvas_sdk.effects.payment_processor import (
      CardTransaction,
      PaymentProcessorForm
    )
    from canvas_sdk.templates import render_to_string
    from canvas_sdk.v1.data import Patient
    def charge(
        self,
        amount: Decimal,
        token: str,
        patient: Patient | None = None,
        **kwargs: Any
    ) -> CardTransaction:
        payload = {
            "token": token,
            "amount": int(amount * 100),
            "currency": "usd",
            "metadata": {"patient_id": patient.id},
        }
        result = self.api.charge(payload)
        return CardTransaction(
            transaction_id=result["transaction_id"],
            success=True,
            api_response=result,
        )
    ```
####  Additional Context 
The `charge` method accepts `**kwargs` which can contain additional context passed from the payment form. This is useful when you need to pass extra information from the tokenization response (e.g., payment method details, transaction metadata) to the charge handler.
The additional context is passed via the second argument of `setToken` in your form's JavaScript (see Form Workflow). Canvas processes the `additional_context` as follows:
Input Type | Example | Passed to `charge` as  
---|---|---  
JSON object string | `'{"key": "value"}'` | `key="value"` (unpacked as kwargs)  
`None` | `null` | `additional_context=None`  
Plain string | `"some text"` | `additional_context="some text"`  
JSON number string | `"123"` | `additional_context=123`  
JSON boolean string | `"true"` | `additional_context=True`  
Example using additional context:
    ```python
    from decimal import Decimal
    from typing import Any
    from canvas_sdk.effects.payment_processor import (
      CardTransaction,
      PaymentProcessorForm
    )
    from canvas_sdk.templates import render_to_string
    from canvas_sdk.v1.data import Patient
    def charge(
        self,
        amount: Decimal,
        token: str,
        patient: Patient | None = None,
        **kwargs: Any
    ) -> CardTransaction:
        # Access additional context from kwargs
        # If setToken was called with a JSON object like {"zip_code": "60007", "city": "Chicago"},
        # these will be available as kwargs
        zip_code = kwargs.get("zip_code")
        city = kwargs.get("city")
        # Or if a non-dict value was passed, it will be in additional_context
        raw_context = kwargs.get("additional_context")
        # ... rest of implementation
    ```
* * *
###  Managing Payment Methods 
These methods define how Canvas lists, stores, and deletes saved cards associated with a patient.
####  List 
Returns the patient's saved cards as a list of [`PaymentMethod`](/sdk/payment-processor-effect/#paymentmethod) effects, which Canvas renders when displaying stored payment methods.
    ```python
    from canvas_sdk.effects.payment_processor import PaymentMethod
    from canvas_sdk.v1.data import Patient
    def payment_methods(self, patient: Patient | None = None) -> list[PaymentMethod]:
       return [
                PaymentMethod(
                    payment_method_id="pm_1",
                    brand="Visa",
                    expiration_year=2025,
                    expiration_month=12,
                    card_holder_name="John Doe",
                    postal_code="12345",
                    card_last_four_digits="1234",
                )
            ]
    ```
####  Add 
Stores a new card for the patient using the tokenized card data and returns an [`AddPaymentMethodResponse`](/sdk/payment-processor-effect/#addpaymentmethodresponse) effect indicating whether the card was saved.
    ```python
    from typing import Any
    from canvas_sdk.effects.payment_processor import AddPaymentMethodResponse
    from canvas_sdk.v1.data import Patient
    def add_payment_method(self, token: str, patient: Patient, **kwargs: Any) -> AddPaymentMethodResponse:
        return AddPaymentMethodResponse(success=True)
    ```
#####  Additional Context 
The `add_payment_method` method accepts `**kwargs` which can contain additional context passed from the add card form. This is useful when you need to pass extra information from the tokenization response to the `add_payment_method` handler.
The additional context is passed via the second argument of `setToken` in your form's JavaScript (see Form Workflow). Canvas processes the `additional_context` as follows:
Input Type | Example | Passed to `add_payment_method` as  
---|---|---  
JSON object string | `'{"key": "value"}'` | `key="value"` (unpacked as kwargs)  
`None` | `null` | `additional_context=None`  
Plain string | `"some text"` | `additional_context="some text"`  
JSON number string | `"123"` | `additional_context=123`  
JSON boolean string | `"true"` | `additional_context=True`  
Example using additional context:
    ```python
    from typing import Any
    from canvas_sdk.effects.payment_processor import AddPaymentMethodResponse
    from canvas_sdk.v1.data import Patient
    def add_payment_method(self, token: str, patient: Patient, **kwargs: Any) -> AddPaymentMethodResponse:
        # Access additional context from kwargs
        # If setToken was called with a JSON object like {"zip_code": "60007", "city": "Chicago"},
        # these will be available as kwargs
        zip_code = kwargs.get("zip_code")
        city = kwargs.get("city")
        # Or if a non-dict value was passed, it will be in additional_context
        raw_context = kwargs.get("additional_context")
        # ... rest of implementation
        return AddPaymentMethodResponse(success=True)
    ```
####  Remove 
Deletes the patient's saved card identified by `token` and returns a [`RemovePaymentMethodResponse`](/sdk/payment-processor-effect/#removepaymentmethodresponse) effect indicating whether the card was removed.
    ```python
    from canvas_sdk.effects.payment_processor import RemovePaymentMethodResponse
    from canvas_sdk.v1.data import Patient
    def remove_payment_method(self, token: str, patient: Patient) -> RemovePaymentMethodResponse:
        return RemovePaymentMethodResponse(success=True)
    ```
* * *
##  Form Workflow 
Payment and Add Card forms are rendered as inner HTML inside Canvas. These forms must implement the following communication contract via JavaScript:
###  1\. Notify readiness 
This should be called once the form has fully loaded and is ready for user interaction. Canvas waits for this signal to show the form.
    ```js
    window.parent.setFormIsReady();
    ```
###  2\. Submit token after success 
Once the card details are validated and tokenized, this method must be called to pass the token back to Canvas. This token will then be used to charge or store the card.
    ```js
    window.parent.setToken("tok_123abc");
    ```
You can optionally pass a second argument with additional context that will be forwarded to the `charge` or `add_payment_method` methods. This is useful for passing extra data from the tokenization response:
    ```js
    // Pass additional context as a JSON-serializable object
    window.parent.setToken("tok_123abc", { zip_code: "111", city: "Chicago" });
    // Or pass the raw tokenization result
    const result = await paymentProvider.tokenize(cardDetails);
    window.parent.setToken(result.token, result);
    ```
The additional context will be available in the `charge` and `add_payment_method` method's `**kwargs`. See the Charge section for details on how different value types are handled.
###  3\. Report validation status 
Call this every time the validity of the form changes (e.g., input becomes valid or invalid). Canvas uses this to enable or disable the ability to submit the form programmatically.
    ```js
    window.parent.setFormIsValid(true); // or false
    ```
###  4\. Handle errors 
Call this to notify Canvas of any form-level errors (e.g., failed tokenization, invalid input). The message should help users understand what went wrong.
    ```js
    window.parent.setError("Invalid card details");
    ```
###  5\. Submit button 
The HTML form must include a button with `id="submit"`. This button is required, should remain hidden from the user, and will be clicked programmatically by Canvas when the form is considered valid and ready to be submitted.
    ```html
    <button type="submit" id="submit" hidden>Submit</button>
    ```
###  6\. Define teardown function 
This function will be called automatically when the form is unmounted or replaced. Use it to remove any event listeners or free up memory/resources.
    ```js
    window.teardown = () => {
      // Cleanup listeners, state, etc.
    };
    ```
* * *
##  Tokenization Flow 
Tokenization is the process where the sensitive card information is securely transformed into a token that can be safely used by Canvas to perform charges or save cards.
When implementing a payment processor, it's expected that your HTML forms integrate with your provider's frontend SDK to:
  - Collect and validate card input
  - Tokenize the card using the provider's secure API
  - Call `window.parent.setToken(token)` with the result
Canvas does not handle raw card input. Tokenization **must** be handled entirely by the payment provider's frontend tools within the form.
Before building a plugin, ensure:
  - Your provider offers secure frontend tokenization
  - You can integrate with their JavaScript SDK
  - You can submit token data from the form via `setToken`
* * *
##  Example Plugin 
For a complete, working reference, see the [PayTheory Payment Processor](/sdk/example-paytheory_payment_processor/) example plugin. It implements a custom `CardPaymentProcessor` that replaces the standard Stripe integration with [Pay Theory](https://paytheory.com), covering tokenization forms, charging, and saved payment method management.
----- END PAGE https://docs.canvasmedical.com/sdk/handlers-payment-processors/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/handlers-simple-api-commands/
**Any time you want to write a[command](/sdk/commands/) to a patient's chart over HTTP, this is already built for you.** `CommandAPI` is a [SimpleAPI](/sdk/handlers-simple-api-http/) that reads a request body onto a command, validates it against that command, and emits the effects. You declare the routes and who may reach them; it does the rest.
Reach for it whenever something outside Canvas needs to write to a chart — a patient-facing intake form, a device reporting readings, an internal tool your staff already work in, or a service that turns its own records into chart entries. Written by hand, each of those means parsing a body, mapping it onto a command, deciding how to report every way it can be wrong, and keeping your own record of which command you wrote. None of that is yours to write anymore.
For a walkthrough — including how to check that the caller may write to a particular note — see [Writing Commands Over HTTP](/guides/writing-commands-over-http/).
##  Quickstart 
    ```python
    from canvas_sdk.commands import HistoryOfPresentIllnessCommand
    from canvas_sdk.commands.api import CommandAPI
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.simple_api import Response
    from canvas_sdk.handlers.simple_api import StaffSessionAuthMixin, api
    class HistoryOfPresentIllnessAPI(StaffSessionAuthMixin, CommandAPI):
        PREFIX = "/v1"
        @api.post("/hpi")
        def insert(self) -> list[Response | Effect]:
            return self.originate(HistoryOfPresentIllnessCommand)
    ```
That class is five lines, and both of its base classes are load-bearing.
###  What it inherits from SimpleAPI 
`CommandAPI` is a full [SimpleAPI](/sdk/handlers-simple-api-http/). The Quickstart shows one shape — a single `POST` under a `PREFIX` — but that is just this example. Anything you can build with a SimpleAPI you can build here:
  - **As many routes as you like** , declared with `@api.get`, `@api.post`, `@api.put`, `@api.patch` and `@api.delete`. `PREFIX` is optional and prefixes them all.
  - **Path parameters.** A segment written `<name>` in a route path is a placeholder — `@api.put("/hpi/<command_id>")` matches a request to `/hpi/2588aa22-9d0e-4f1f-9b28-6f0e6a1c9a10`, and what it matched is available as `self.request.path_params["command_id"]`. That is how the routes below say _which_ command to edit or act on: the id comes from the URL rather than the body. A value is always a string, is never empty, and covers one path segment only — it cannot contain a `/`.
  - **The whole request.** `self.request` carries the method, path, headers, query parameters, the raw body, parsed JSON, text, and `multipart/form-data` parts including file uploads.
  - **Any response you want to send.** A handler always returns the ordinary SimpleAPI list of responses and effects. `originate`, `edit` and `action` each return that list already built — which is why the handlers here read `return self.originate(...)` with no brackets of their own. When you want your own response, you build the list yourself, as the `act` route does below with `[JSONResponse(...)]`, or splice onto theirs: `return [*self.originate(...), my_effect]`.
  - **Authentication.** You decide which method to use — see [Authentication](/sdk/handlers-simple-api-http/#authentication) for the schemes available.
One difference worth knowing: `CommandAPI` extends `SimpleAPI`, not [`SimpleAPIRoute`](/sdk/handlers-simple-api-http/#simpleapiroute). A class declares `PREFIX` and decorated route handlers rather than a single `PATH` with `get` and `post` methods.
What `CommandAPI` adds on top is the three methods below — `originate`, `edit` and `action` — and nothing else. Those three names are reserved: a route handler may not reuse one, and SimpleAPI raises at class-definition time if you try.
###  Where authentication comes from 
`CommandAPI` does not **choose** your authentication — which callers may write commands is your plugin's decision, not the base's. The schemes themselves are provided: declare one by listing it _before_ `CommandAPI` in the bases, from `StaffSessionAuthMixin`, `PatientSessionAuthMixin`, `APIKeyAuthMixin` and `BasicAuthMixin`. See [Authentication mixins](/sdk/handlers-simple-api-http/#authentication-mixins) for what each one needs, or write `authenticate` yourself.
Nothing is left open in the meantime. SimpleAPI's own `authenticate` returns `False`, so an endpoint that declares no scheme refuses every request rather than admitting anyone.
**That default is also why the order of the base classes matters, and why getting it wrong fails quietly** — whichever class Python reaches first decides:
Base classes | Result  
---|---  
`(StaffSessionAuthMixin, CommandAPI)` | The mixin answers first. Authentication works as you expect.  
`(CommandAPI, StaffSessionAuthMixin)` | SimpleAPI answers first, and it refuses everything. **Every request is rejected** , with no error to tell you why.  
> **Warning:** An authentication mixin establishes _who_ the caller is, not _what_ they may write. `StaffSessionAuthMixin` only checks that the session belongs to a staff member — it does not consider roles, and it says nothing about whether that person may write to the note in the request body. For that check, see [Writing Commands Over HTTP](/guides/writing-commands-over-http/). 
##  A complete endpoint 
The Quickstart creates a command. Most endpoints also need to change one afterwards, which means three routes — create, update, and apply an action such as committing it. All three live on one class, and each is a single call:
    ```python
    from http import HTTPStatus
    from canvas_sdk.commands import HistoryOfPresentIllnessCommand
    from canvas_sdk.commands.api import CommandAPI
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.simple_api import JSONResponse, Response
    from canvas_sdk.handlers.simple_api import StaffSessionAuthMixin, api
    # The actions this endpoint will pass on. Checked against the request so a caller
    # cannot name an arbitrary attribute of the command class.
    ALLOWED_ACTIONS = {"commit", "delete", "enter_in_error"}
    class HistoryOfPresentIllnessAPI(StaffSessionAuthMixin, CommandAPI):
        PREFIX = "/v1"
        @api.post("/hpi")
        def create(self) -> list[Response | Effect]:
            return self.originate(HistoryOfPresentIllnessCommand)
        @api.put("/hpi/<command_id>")
        def update(self) -> list[Response | Effect]:
            return self.edit(HistoryOfPresentIllnessCommand, self.request.path_params["command_id"])
        @api.post("/hpi/<command_id>/<action>")
        def act(self) -> list[Response | Effect]:
            action = self.request.path_params["action"]
            if action not in ALLOWED_ACTIONS:
                return [
                    JSONResponse(
                        {"error": "unsupported action", "allowed": sorted(ALLOWED_ACTIONS)},
                        status_code=HTTPStatus.BAD_REQUEST,
                    )
                ]
            return self.action(
                HistoryOfPresentIllnessCommand,
                self.request.path_params["command_id"],
                action,
            )
    ```
Staging a note entry, committing it, and retracting it, against that endpoint:
    ```shell
    # Create it, staged for a human to finish.
    curl -X POST https://example.canvasmedical.com/plugin-io/api/my_plugin/v1/hpi \
      -H 'Content-Type: application/json' \
      -d '{
            "note_id": "d2194110-5c9a-4842-8733-ef09ea5ead11",
            "values": {"narrative": "Patient reports a cough for three days."}
          }'
    # 201 {"command_uuid": "2588aa22-9d0e-4f1f-9b28-6f0e6a1c9a10", "committed": false}
    # Revise it.
    curl -X PUT https://example.canvasmedical.com/plugin-io/api/my_plugin/v1/hpi/2588aa22-9d0e-4f1f-9b28-6f0e6a1c9a10 \
      -H 'Content-Type: application/json' \
      -d '{"values": {"narrative": "Patient reports a dry cough for three days."}}'
    # 200 {"command_uuid": "2588aa22-9d0e-4f1f-9b28-6f0e6a1c9a10", "mode": "edit"}
    # Commit it.
    curl -X POST https://example.canvasmedical.com/plugin-io/api/my_plugin/v1/hpi/2588aa22-9d0e-4f1f-9b28-6f0e6a1c9a10/commit
    # 200 {"command_uuid": "2588aa22-9d0e-4f1f-9b28-6f0e6a1c9a10", "mode": "commit"}
    # Retract it. A committed command cannot be edited, so entering it in error is
    # how you take it back - then originate its replacement.
    curl -X POST https://example.canvasmedical.com/plugin-io/api/my_plugin/v1/hpi/2588aa22-9d0e-4f1f-9b28-6f0e6a1c9a10/enter_in_error
    # 200 {"command_uuid": "2588aa22-9d0e-4f1f-9b28-6f0e6a1c9a10", "mode": "enter_in_error"}
    ```
The order matters: `enter_in_error` needs a **committed** command, so it only works after the commit above. Sent against the staged command it would have been refused — see State.
To serve every command from one endpoint instead of one class per command, see [Serving every command from one endpoint](/guides/writing-commands-over-http/#serving-every-command-from-one-endpoint) in the guide — the command is an argument, so a dict of them and a path parameter is all it takes.
##  Methods 
Each returns the effects and the response for you to return from the route handler. You return the list as-is; nothing else is required of the handler.
###  originate 
`originate(model)` creates a command from the request body.
Body field | Type | Required | Description  
---|---|---|---  
`note_id` | _string_ | `true` | The id of the [Note](/sdk/data-note/#note) to write the command into.  
`values` | _object_ | `false` | The command's own fields, named as the command declares them. See [Commands](/sdk/commands/) for the fields each command takes.  
`command_id` | _string_ | `false` | An id of your choosing for the new command, instead of the one the response returns.  
`commit` | _boolean_ | `false` | Commit the command as well as creating it. Defaults to `false`, leaving it staged for a human to finish.  
`metadata` | _object_ | `false` | A flat `{"key": "value"}` map attached to the command. See [Command metadata](/sdk/effect-command-metadata/).  
Responds `201` with the id of the command it wrote and whether it was committed:
    ```json
    { "command_uuid": "2588aa22-9d0e-4f1f-9b28-6f0e6a1c9a10", "committed": false }
    ```
A body using every field:
    ```json
    {
      "note_id": "d2194110-5c9a-4842-8733-ef09ea5ead11",
      "command_id": "2588aa22-9d0e-4f1f-9b28-6f0e6a1c9a10",
      "commit": true,
      "values": { "narrative": "Patient reports a dry cough for three days." },
      "metadata": { "source": "intake-form", "submission": "8871" }
    }
    ```
If anything is wrong with the request, nothing is written and you get a `400` instead. A value that the command refuses is reported against the field it came from:
    ```json
    {
      "error": "Validation failed",
      "validation_errors": [
        { "field": "values.narrative", "message": "String should have at most 512 characters" }
      ]
    }
    ```
A problem with the envelope rather than the values reads the same way, without the `values.` prefix — here a body that left `note_id` out:
    ```json
    {
      "error": "Validation failed",
      "validation_errors": [
        { "field": "note_id", "message": "Field required" }
      ]
    }
    ```
Every field at fault is reported at once, so a caller sees the whole list rather than fixing one problem per round trip. The other statuses this route can answer with are in Responses.
###  edit 
`edit(model, command_id)` updates a **staged** command. The command is addressed by id in your route, and the body carries `values` and `metadata`:
    ```json
    { "values": { "narrative": "Patient reports a dry cough for three days." } }
    ```
> **Warning:** `values` replaces the command's fields as a whole and is re-validated in full — it is not a patch. A field you leave out is not left alone; send the complete set of values you want the command to end up with. 
Responds `200`:
    ```json
    { "command_uuid": "2588aa22-9d0e-4f1f-9b28-6f0e6a1c9a10", "mode": "edit" }
    ```
Three things can go wrong, and each is checked before anything is written.
**The id matches no command of this type** — a `404`:
    ```json
    { "error": "No hpi command with that id" }
    ```
**The command is not staged** — a `400` naming the state it is in and the one the operation needed. A committed command cannot be edited; enter it in error and originate its replacement instead:
    ```json
    {
      "error": "a committed command cannot be edited",
      "state": "committed",
      "required_state": "staged",
      "validation_errors": []
    }
    ```
**A value is wrong for its field** — a `400` in the same shape `originate` returns, reported against `values.<field>`. A body that is not a JSON object at all is a `400` too, carrying `"Request body must be a JSON object"`.
See State for which operations each state allows, and Responses for the full list of statuses.
###  action 
`action(model, command_id, action)` runs one of the command's own methods. It takes no request body, and responds `200` naming what it did:
    ```json
    { "command_uuid": "2588aa22-9d0e-4f1f-9b28-6f0e6a1c9a10", "mode": "commit" }
    ```
`action` names a method on the command class, and each one builds the corresponding [command effect](/sdk/effects/#commands):
Action | What it does | Required state  
---|---|---  
`commit` | Signs the staged command into the note. | staged  
`delete` | Removes the staged command from the note. | staged  
`enter_in_error` | Marks a committed command as entered in error. | committed  
`review` | Places the command into review status. | set by the command — see State  
`send` | Transmits the command to an external system. | set by the command — see State  
`delegate` | Delegates the order to someone else to complete. | set by the command — see State  
`sign` | Signs the order. | set by the command — see State  
**No command supports all of them, and not even`commit` is universal.** Which actions a command accepts is listed per command in [Commands](/sdk/effects/#commands) — check there before wiring a route, because an action the command does not support is a `400`. The ones worth knowing up front:
  - `review` and `send` belong to [Prescribe](/sdk/commands/#prescribe), [Refill](/sdk/commands/#refill) and [Adjust Prescription](/sdk/commands/#adjustprescription), and `send` also to [Lab Order](/sdk/commands/#laborder). Those four are **not** committed — sending is how they are finished.
  - `delegate` and `sign` belong to [Imaging Order](/sdk/commands/#imagingorder) and [Refer](/sdk/commands/#refer), which are not committed either.
  - [Reason for Visit](/sdk/commands/#reasonforvisit) supports `originate`, `edit` and `delete` only.
  - [Chart Section Review](/sdk/commands/#chartsectionreview) is committed as it is originated, so it takes `originate` and nothing else.
  - [Custom commands](/sdk/commands-custom-command/) render read-only content, so they are neither edited nor committed.
An action the command class does not have is a `400`:
    ```json
    { "error": "HistoryOfPresentIllnessCommand does not support the 'review' action", "validation_errors": [] }
    ```
> **Danger:** The action usually arrives from the request, and it is passed to `getattr` on the command. Check it against a set you control first — as `ALLOWED_ACTIONS` does above — or a caller can name any attribute of the command class. 
##  Field values 
`values` carries the command's own fields, named exactly as the command declares them. Each command's fields are listed under [Commands](/sdk/commands/) — `values` accepts the same set, so that page is the reference for what may go in here.
Commands parse leniently, so JSON's own types are enough. Posting to an [Allergy](/sdk/commands/#allergy) endpoint:
    ```json
    {
      "note_id": "d2194110-5c9a-4842-8733-ef09ea5ead11",
      "values": {
        "narrative": "Hives within an hour of eating shellfish.",
        "severity": "mild",
        "approximate_date": "2026-08-04"
      }
    }
    ```
  - `narrative` is a plain string, capped at 512 characters by the command.
  - `severity` is an enum, given as its value — `"mild"`, not `AllergyCommand.Severity.MILD`.
  - `approximate_date` is a date, given as an ISO string.
  - A number may arrive as a number or as a string: `12` and `"12"` are both read as `12`.
A key that is not a field on the command is **refused** , not dropped — silently ignoring a typo would write a blank command over it. Every unknown key is reported at once, against `values.<field>`:
    ```json
    {
      "error": "Validation failed",
      "validation_errors": [
        { "field": "values.narative", "message": "Unexpected field" }
      ]
    }
    ```
`note_id`, `command_id`, `commit` and `metadata` are the envelope, not fields — they sit alongside `values`, not inside it, and they are kept out of what the command writes.
###  Structured fields 
Not every field is a scalar. A number of commands take a structured value — an object, or a list of them — and each arrives as ordinary nested JSON. You do not construct the SDK type; you send its shape:
Type | Example value  
---|---  
[`Allergen`](/sdk/commands/#allergy-allergen) | `{"concept_id": 91, "concept_type": 2}`  
[`Coding`](/sdk/commands/#coding) | `{"system": "http://snomed.info/sct", "code": "44054006", "display": "Diabetes mellitus type 2"}`  
[`ClinicalQuantity`](/sdk/commands/#clinicalquantity) | `{"representative_ndc": "0093-1023", "ncpdp_quantity_qualifier_code": "C48542"}`  
[`ServiceProvider`](/sdk/commands/#serviceprovider) | `{"first_name": "Ada", "last_name": "Lovelace", "specialty": "Cardiology", "practice_name": "Mercy"}`  
[`TaskAssigner`](/sdk/commands/#taskassigner) | `{"to": "staff", "id": 5}`  
[`TestValue`](/sdk/commands/#poclabtest-testvalue) | `[{"label": "Glucose", "value": "98"}]`  
[`CompoundMedicationData`](/sdk/commands/#prescribe-compoundmedicationdata) | `{"formulation": "cream", "potency_unit_code": "C28253", "controlled_substance": "0"}`  
[`Answer` and `Selection`](/sdk/commands/#questionnaire-answer) | `[{"question_id": 15, "response": [{"option_id": 201, "comment": "left side"}]}]`  
Each type's own fields are documented with the command that uses it — follow the link for what is required and what each field means.
> **Warning:** An enum nested inside one of these is given by its **value** , not its name. `Allergen.concept_type` takes `2`, not `"MEDICATION"`; `TaskAssigner.to` takes `"staff"`, not `"STAFF"`. A name is refused with an `enum` validation error naming the values it will accept. 
Answering a questionnaire is the largest of these, and the whole [`answers`](/sdk/commands/#questionnaire) list goes in `values` like any other field:
    ```json
    {
      "note_id": "d2194110-5c9a-4842-8733-ef09ea5ead11",
      "values": {
        "questionnaire_id": "c1a2b3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
        "answers": [
          { "question_id": 12, "response": "Three times a week." },
          { "question_id": 13, "response": 42 },
          { "question_id": 14, "response": 101 },
          { "question_id": 15, "response": [{ "option_id": 201 }, { "option_id": 202, "comment": "left side" }] }
        ]
      }
    }
    ```
The same route serves [Review of Systems](/sdk/commands/#review-of-systems), [Structured Assessment](/sdk/commands/#structuredassessment) and [Physical Exam](/sdk/commands/#physicalexam), which take `answers` too.
##  Metadata 
`metadata` attaches a flat `{"key": "value"}` map to the command. It is stored as sent and nothing in Canvas interprets it, which makes it the place to record what the entry meant on your side:
    ```json
    {
      "note_id": "d2194110-5c9a-4842-8733-ef09ea5ead11",
      "values": { "narrative": "Patient reports a dry cough for three days." },
      "metadata": { "source": "intake-form", "submission": "8871", "version": "2" }
    }
    ```
Both `originate` and `edit` accept it. Values must be strings — send `"8871"`, not `8871`.
Each pair becomes an `upsert_metadata` effect applied after the effect that writes the command, so a key sent twice ends up holding the last value. See the [Command Metadata effect](/sdk/effect-command-metadata/) for how the same storage is written from a plugin, the [Command Metadata Create form effect](/sdk/command-metadata-create-form-effect/) for collecting it from a user in the chart, and [CommandMetadata](/sdk/data-command/#commandmetadata) for reading it back.
##  Responses 
Status | When  
---|---  
`201` | The command was created.  
`200` | An edit or an action was applied.  
`400` | The body is not a JSON object, a value is wrong for its field, or the command's state does not allow the operation.  
`404` | No command **of this type** has that id. The lookup is scoped by command type, so an id belonging to a different command is a miss rather than a cross-type edit.  
`409` | The `command_id` you chose already belongs to a command.  
**Every rejection has the same shape** — an `error` summarising it and a `validation_errors` list, empty when nothing field-specific was at fault. A caller can render all of them the same way.
A value that is wrong for its field:
    ```json
    {
      "error": "Validation failed",
      "validation_errors": [
        { "field": "values.narrative", "message": "Input should be a valid string" }
      ]
    }
    ```
A body that is not a JSON object at all:
    ```json
    { "error": "Request body must be a JSON object", "validation_errors": [] }
    ```
An id matching no command of this endpoint's type:
    ```json
    { "error": "No hpi command with that id" }
    ```
That `404` covers three cases at once, deliberately: no command has the id, a command has it but is of a different type, or the id is not a well-formed UUID. The lookup is scoped to the command type this endpoint serves, so an id belonging to a Plan command reaching a History of Present Illness endpoint is a miss rather than a cross-type edit.
###  Choosing the command's id 
Passing `command_id` is worth doing when the thing you are writing already has an identity on your side: you can then ask whether it reached the chart by that id rather than storing a mapping.
**It has to be a real UUID.** It is not a free-text key of your own, so generate one rather than composing it:
    ```shell
    uuidgen                        # 2588AA22-9D0E-4F1F-9B28-6F0E6A1C9A10
    python -c 'import uuid; print(uuid.uuid4())'
    node  -e 'console.log(crypto.randomUUID())'
    ```
Anything that will not parse as a UUID is a `400` before the command is looked at — `"order-8871"` gives `Input should be a valid UUID, invalid character: found 'o' at 1`, and `"8871"` gives `Input should be a valid UUID, invalid length`. The 32-character form without dashes is accepted, so either `2588aa22-9d0e-4f1f-9b28-6f0e6a1c9a10` or `2588aa229d0e4f1f9b286f0e6a1c9a10` will do.
If your own identifier is not a UUID, derive one from it deterministically — `uuid.uuid5()` over a namespace and your key — so the same record always produces the same command id.
Posting the same id twice answers `409` rather than writing a second command, which makes a retry safe:
    ```json
    { "error": "a command already has that id", "command_uuid": "2588aa22-…", "validation_errors": [] }
    ```
###  State 
Some operations only apply to a command in a particular state. Which operations a command accepts at all is a separate question, answered per command by the [command type table](/sdk/effects/#commands).
Operation | Required state  
---|---  
`edit` | staged  
`delete` | staged  
`commit` | staged  
`enter_in_error` | committed  
`review`, `send`, `delegate`, `sign` | Whatever the command requires. `CommandAPI` sets no state for these; the command enforces its own rules as it builds the effect.  
A refusal is a `400`, naming the state the command is in and the one it needed:
    ```json
    {
      "error": "a committed command cannot be edited",
      "state": "committed",
      "required_state": "staged",
      "validation_errors": []
    }
    ```
##  Naming route handlers 
A route handler cannot be called `originate`, `edit` or `action` — those are the methods you are calling. Name handlers for the HTTP verb they serve.
----- END PAGE https://docs.canvasmedical.com/sdk/handlers-simple-api-commands/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/handlers-simple-api-http/
The Canvas SDK provides a way to define an HTTP API with any number of endpoints in your instance. Developers can define the routes and implement the code that will handle incoming HTTP requests.
This feature allows developers to create endpoints that can receive webhook events from other services. An endpoint receiving a request can invoke Effects in a Canvas instance, send another request to a different service, or simply return a response back to the requester.
##  Quickstart 
Follow the instructions in [Your First Plugin (with Claude Code)](https://docs.canvasmedical.com/guides/your-first-plugin-with-claude-code/) to create a plugins project. For this exercise, use `my_api` as your project (i.e. plugin) name.
Open `CANVAS_MANIFEST.json` in your editor. You can modify filenames, directory structures, and class names as you see fit in your project, but for this exercise, we are just going to set the value at `components -> handlers -> 0 -> class` to be `my_api.handlers.my_handler:MyAPI`.
We're going to need a secret value for authentication. The instructions for declaring secrets are outlined on the [Your First Plugin (Manual)](https://docs.canvasmedical.com/guides/your-first-plugin/) page. Declare a secret in `CANVAS_MANIFEST.json` named `my-api-key`.
Open `my_api/handlers/my_handler.py` and replace the contents of the file with this code:
    ```python
    from hmac import compare_digest
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.simple_api import JSONResponse, Response
    from canvas_sdk.handlers.simple_api import APIKeyCredentials, SimpleAPIRoute
    class MyAPI(SimpleAPIRoute):
        PATH = "/routes/hello-world"
        def authenticate(self, credentials: APIKeyCredentials) -> bool:
            provided_api_key = credentials.key
            api_key = self.secrets["my-api-key"]
            # compare_digest requires bytes, so we must encode the strings
            return compare_digest(provided_api_key.encode(), api_key.encode())
        def get(self) -> list[Response | Effect]:
            return [
                JSONResponse({"message": "Hello world!"})
            ]
    ```
The next step is to deploy your plugin; the instructions for doing so are on the [Your First Plugin (Manual)](https://docs.canvasmedical.com/guides/your-first-plugin/) page.
You can see in the code above that the `authenticate` method is going to authenticate using API key authentication. We've already declared the secret, so now we need to generate a value and set it on your instance. You can generate an API key like this:
    ```shell
    python -c "import secrets; print(secrets.token_hex(16))"
    ```
Copy the value that it prints out and set the value for `my-api-key` in your plugin secrets on your instance.
Now that your plugin is deployed and your secret is set, you can send requests to your endpoint with `curl`. The `curl` command would look like the following (note that you will need to supply your instance name and API key):
    ```shell
    curl --location 'https://<instance-name>.canvasmedical.com/plugin-io/api/my_api/routes/hello-world' \
         --header 'Authorization: <api-key>'
    ```
##  Defining APIs 
The Canvas SDK offers two styles for defining API endpoints. To implement an API endpoint or set of endpoints using one of the two styles, your handler will simply inherit from a specific base class. The following HTTP verbs are supported:
  - GET
  - POST
  - PUT
  - DELETE
  - PATCH
###  SimpleAPIRoute 
For handlers that inherit from **SimpleAPIRoute** , you set a class variable in your handler called `PATH` as in the example above, and then implementations of the HTTP verbs you wish to support on that path. The method names will match the names of the HTTP verbs, but lowercased.
The plugin name and the `PATH` value together will form the unique part of the full URL for your endpoint. The format of the full URL will be:
`https://<instance-name>.canvasmedical.com/plugin-io/api/<plugin-name>/<PATH>`
We can adapt the previous example to add a POST endpoint for the same route on the same handler:
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.simple_api import JSONResponse, Response
    from canvas_sdk.handlers.simple_api import APIKeyCredentials, SimpleAPIRoute
    class MyAPI(SimpleAPIRoute):
        PATH = "/routes/hello-world"
        def authenticate(self, credentials: APIKeyCredentials) -> bool:
            ...
        def get(self) -> list[Response | Effect]:
            return [
                JSONResponse({"message": "Hello world from my GET endpoint!"})
            ]
        def post(self) -> list[Response | Effect]:
            return [
                JSONResponse({"message": "Hello world from my POST endpoint!"})
            ]
    ```
The handler can now respond to both GET and POST requests at `/routes/hello-world`.
###  SimpleAPI 
For handlers that inherit from **SimpleAPI** , the syntax is a little different. You can include any number of endpoints in your handler class, and you can name your route handling methods anything you wish. Here is an example:
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.simple_api import JSONResponse, Response
    from canvas_sdk.handlers.simple_api import APIKeyCredentials, SimpleAPI, api
    class MyAPI(SimpleAPI):
        PREFIX = "/routes"
        def authenticate(self, credentials: APIKeyCredentials) -> bool:
            ...
        @api.get("/hello-world")
        def hello_world_get(self) -> list[Response | Effect]:
            return [
                JSONResponse({"message": "Hello world from my GET endpoint!"})
            ]
        @api.post("/hello-world")
        def hello_world_post(self) -> list[Response | Effect]:
            return [
                JSONResponse({"message": "Hello world from my POST endpoint!"})
            ]
        @api.get("/goodbye")
        def goodbye_get(self) -> list[Response | Effect]:
            return [
                JSONResponse({"message": "Goodbye from my GET endpoint!"})
            ]
    ```
This syntax will be familiar if you have used Python API frameworks like `Flask` or `FastAPI`. The decorator functions are named for the HTTP verb you wish to implement on the route, and the URL path is passed into the decorator function. If you have many endpoints that you wish to share the same authentication, this syntax may be more convenient.
You can also specify a path `PREFIX` value for endpoint grouping purposes, as shown in the example above. If you have multiple endpoints that will all have the same path prefix, you can specify it by setting a value for `PREFIX`. With `PREFIX` set, each endpoint does not have to individually specify the `/routes` portion of the URL path.
###  Path patterns 
If you want to set up an endpoint that will respond to requests where the path matches a pattern rather than an exact string, you can use a path pattern. This is common in cases where the path of an endpoint contains a resource identifier.
You can specify a path pattern by by denoting any number of the path parameters in the path using `<>` syntax, with the name of the path parameters in between the angle brackets. Path parameter names must be be unique within the path. They can also be specified in the path prefix (for **SimpleAPI** handlers).
Path parameters will be extracted from the path and will be available on the request object in the `path_params` attribute.
In the example below, the value `id` is specified as part of the path, and can be accessed by the handler:
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.simple_api import JSONResponse, Response
    from canvas_sdk.handlers.simple_api import APIKeyCredentials, SimpleAPIRoute
    class MyAPI(SimpleAPIRoute):
        PATH = "/routes/hello-world/<id>"
        def authenticate(self, credentials: APIKeyCredentials) -> bool:
            ...
        def get(self) -> list[Response | Effect]:
            id_ = self.request.path_params["id"]
            return [
                JSONResponse(
                    {
                        "message": "Hello world from my GET endpoint!",
                        "id": id_
                    }
                )
            ]
    ```
####  Path matching 
When you specify routes using path patterns, it is possible that multiple endpoints may match with a request. This has a few implications that need to be considered, because only one endpoint can provide a response.
If the endpoints that match are all part of the same handler class, then the request will be handled by the endpoint that appears highest up in the class definition, i.e. the one that is defined first. Consider two endpoints specified to match the following patterns:
    /routes/hello-world/current-user
    /routes/hello-world/<id>
The first uses an exact match, and the second uses a pattern. The path `/routes/hello-world/current-user` matches both of those patterns. However, if you register the second endpoint first it would never be possible for a request with the path of `/routes/hello-world/current-user` to match with the endpoint for `/routes/hello-world/current-user`. If you need to define endpoints that use exact matching that may overlap with endpoints defined with path patterns, order must be carefully considered.
If, however, you have defined multiple **SimpleAPIRoute** or **SimpleAPI** handlers, and a request matches with multiple endpoints across these handlers, an error condition will result. There is not a way to specify priority across handlers, so if you need fine-grained control over request routing for endpoints that use path patterns, make sure they are contained within the same handler class.
###  Request objects 
When a handler is invoked to handle an incoming HTTP request, the request object is available as an attribute on the handler. The request method, path, query parameters, content type, and body are all available as attributes on the request object:
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.simple_api import JSONResponse, Response
    from canvas_sdk.handlers.simple_api import APIKeyCredentials, SimpleAPIRoute
    from logger import log
    class MyAPI(SimpleAPIRoute):
        PATH = "/routes/hello-world"
        def authenticate(self, credentials: APIKeyCredentials) -> bool:
            ...
        def get(self) -> list[Response | Effect]:
            request = self.request
            # HTTP method
            method = request.method
            # URL path component
            path = request.path
            # Raw query string
            query_string = request.query_string
            # Query parameters as a key-value mapping
            query_params = request.query_params
            # Request headers
            headers = request.headers
            # Request body content type
            content_type = request.content_type
            # Raw body
            body = request.body
            # JSON body as a Python dictionary (for requests with application/json content types)
            json_body = request.json()
            # Body as plain text
            text_body = request.text()
            # Body parsed as form data
            form_data = request.form_data()
            return [
                JSONResponse({"message": "Hello world!"})
            ]
    ```
####  Key-value mappings 
Attributes on the request object like headers, query parameters, and form data can in most cases be represented by mappings containing key-value pairs (e.g. Python dictionaries) with a small caveat: keys are not required to be unique. Because of this, there can be more than one value per key.
These attributes are represented by a data structure that most of the time will behave like a Python dictionary, unless you want to access the additional values for a key. If you do request the value for a key using standard dictionary syntax, you will get the first value that was encountered for that key. If you want the other values, you will need to use different methods to access them.
Here is an example showing how to access the additional values:
    ```python
    # Request sent to /route?value1=a&value1=b&value2=c
    query_params = request.query_params
    # Get the first value for value1
    value1: str = query_params["value1"]
    # Get all values for value1 with get_list
    value1_all: list[str] = query_params.get_list("value1")
    # Iterate over all query parameters (repeating keys if necessary) with multi_items
    for key, value in query_params.multi_items():
        log.info(f"key:   {key}")
        log.info(f"value: {value}")
    ```
####  Forms 
If your endpoint is set up to accept `application/x-www-form-urlencoded` or `multipart/form-data` data, there is method named `form_data` on the request object that will parse the request body. This method will return a key-value mapping containing `FormPart` objects, each of which represents a subpart of the form.
Every subpart in a form has a name, and these names are the keys in the mapping that is returned by the method. A `FormPart` can represent either a simple string value or a file. A `FormPart` that represents a string will have attributes for `name` and `value`. A `FormPart` that represents a file will have attributes for `name`, `filename`, `content`, `content_type`.
If the content type of a request is `application/x-www-form-urlencoded`, then all `FormPart` objects will represent simple string values. If the content type of a request is `multipart/form-data`, then each `FormPart` object may represent either a simple string value or a file.
Here is an example of how to use the `form_data` method to iterate over the subparts of a request body with form data:
    ```python
    form_data = request.form_data()
    # To iterate over all parts, we have to use the multi_items method because there may be more than
    # one part with the same name
    for name, part in form_data.multi_items():
        log.info(f"part name:    {name}")
        if part.is_file():
            # It's a file
            log.info(f"content:      {part.content}")
            log.info(f"filename:     {part.filename}")
            log.info(f"content type: {part.content_type}")
        else:
            # It's a simple string
            log.info(f"value:        {part.value}")
    ```
If you know the name of the subparts you are looking for, you can also access the subparts directly by looking up the name in the mapping returned by `form_data`:
    ```python
    form_data = request.form_data()
    # Get the first part named "my-part-name"
    part = form_data["my-part-name"]
    # Get all parts named "my-part-name"
    parts_all = form_data.get_list("my-part-name")
    ```
###  Responses 
Endpoint handlers may return zero or one response objects and any number of Effects. Handlers that return multiple response objects will return a **500 Internal Server Error** response back to the requester. If your endpoint does not provide a response object, then the requester will receive a **204 No Content** response.
####  Response types 
Several response types are provided for convenience:
  - HTMLResponse
  - JSONResponse
  - PlainTextResponse
  - Response (for returning raw content)
In addition to the response body, you can also specify the response status code and the response headers.
    ```python
    from http import HTTPStatus
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.simple_api import HTMLResponse, JSONResponse, PlainTextResponse, Response
    from canvas_sdk.handlers.simple_api import APIKeyCredentials, SimpleAPIRoute
    class MyAPI(SimpleAPIRoute):
        PATH = "/routes/hello-world"
        def authenticate(self, credentials: APIKeyCredentials) -> bool:
            ...
        def get(self) -> list[Response | Effect]:
            return [
                HTMLResponse(
                    "<p>Hello world from my GET endpoint!</p>",
                    status_code=HTTPStatus.OK,
                    headers={"My-Header", "my header value"}
                )
            ]
        def post(self) -> list[Response | Effect]:
            return [
                JSONResponse(
                    {"message": "Hello world from my POST endpoint!"},
                    status_code=HTTPStatus.CREATED,
                    headers={"My-Header", "my header value"}
                )
            ]
        def put(self) -> list[Response | Effect]:
            return [
                PlainTextResponse(
                    "Hello world from my PUT endpoint!",
                    status_code=HTTPStatus.ACCEPTED,
                    headers={"My-Header", "my header value"}
                )
            ]
        def patch(self) -> list[Response | Effect]:
            return [
                Response(
                    b'{"message": "Hello world from my PATCH endpoint!"}',
                    status_code=HTTPStatus.NOT_MODIFIED,
                    headers={"My-Header", "my header value"},
                    content_type="application/json"
                )
            ]
    ```
####  Returning Effects 
**SimpleAPI** endpoints can return any number of Effects just like any Canvas plugin; this is why **SimpleAPI** endpoints return a list of items rather than just a single response object.
Any effects present in the list returned by an endpoint will be processed by your Canvas instance, and the response object, if provided, will be sent back to the original requester.
###  Asynchronous requests 
By default, **SimpleAPI** requests are processed synchronously—the caller waits for the plugin to finish executing before receiving a response. If you prefer an immediate acknowledgement instead, include the `Prefer: respond-async` header in your request:
    ```bash
    curl --location 'https://<instance-name>.canvasmedical.com/plugin-io/api/<plugin-name>/<route>' \
         --header 'Authorization: <api-key>' \
         --header 'Prefer: respond-async'
    ```
When this header is present, Canvas will return a **202 Accepted** response right away and continue executing the plugin in the background. Any effects returned by the handler will still be processed by your Canvas instance; however, no response body from the handler will be delivered to the caller.
Note that authentication failures and plugin-not-found errors are always returned synchronously, regardless of this header.
###  Authentication 
Defining an `authenticate` method on your handler is required. By default, **SimpleAPI** handlers will return a **401 Unauthorized** response if no `authenticate` method is defined. The `authenticate` method should return `True` or `False` depending on whether the requester is authenticated.
Please keep in mind that while setting plugins secrets on your instance is out of scope for this guide, best practices would dictate that most `authenticate` methods would use these secrets to authenticate credentials in a request (OAuth being a notable exception). Your secrets can be accessed through the `secrets` attribute on the handler.
Additionally, to assist with adhering to security and cryptography best practices, the Python `hashlib`, `hmac`, and `secrets` modules are available for use.
Examples of how to define `authenticate` methods for various authentication schemes are shown in the next section, but if you are interested in something that is more "batteries included", please skip ahead to the Authentication mixins section below. The API key authentication mixin is a good choice that offers simplicity and good security if you need something to get started.
####  Authentication schemes 
The Canvas SDK can parse and validate the format of the Authentication header automatically for several authentication schemes, but you must authenticate the credentials in your `authenticate` method. You can specify which authentication scheme you want to use for your route or API in the method signature of your `authenticate` method.
#####  Basic 
For Basic authentication, use `BasicCredentials`:
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.simple_api import Response
    from canvas_sdk.handlers.simple_api import BasicCredentials, SimpleAPIRoute
    class MyAPI(SimpleAPIRoute):
        PATH = "/routes/hello-world"
        def authenticate(self, credentials: BasicCredentials) -> bool:
            provided_username = credentials.username
            provided_password = credentials.password
            # Validate provided username and password against a username and password in self.secrets
            ...
        def get(self) -> list[Response | Effect]:
            ...
    ```
#####  Bearer 
For Bearer authentication, use `BearerCredentials`:
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.simple_api import Response
    from canvas_sdk.handlers.simple_api import BearerCredentials, SimpleAPIRoute
    class MyAPI(SimpleAPIRoute):
        PATH = "/routes/hello-world"
        def authenticate(self, credentials: BearerCredentials) -> bool:
            provided_token = credentials.token
            # Validate provided access token via OAuth
            ...
        def get(self) -> list[Response | Effect]:
            ...
    ```
#####  API key 
For API key authentication, use `APIKeyCredentials`:
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.simple_api import Response
    from canvas_sdk.handlers.simple_api import APIKeyCredentials, SimpleAPIRoute
    class MyAPI(SimpleAPIRoute):
        PATH = "/routes/hello-world"
        def authenticate(self, credentials: APIKeyCredentials) -> bool:
            provided_api_key = credentials.key
            # Validate provided key against an API key in self.secrets
            ...
        def get(self) -> list[Response | Effect]:
            ...
    ```
#####  Session 
To authenticate using a logged-in user's session, use `SessionCredentials`:
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.simple_api import Response
    from canvas_sdk.handlers.simple_api import SessionCredentials, SimpleAPIRoute
    class MyAPI(SimpleAPIRoute):
        PATH = "/routes/hello-world"
        def authenticate(self, credentials: SessionCredentials) -> bool:
            logged_in_user = credentials.logged_in_user
            # Structure looks like:
            # {
            #     "id": "abc123",
            #     "type": "Staff"
            # }
            # Where "type" is "Staff" or "Patient"
            # You could authenticate based on type or check to see if the
            # individual is in a particular group or team.
            ...
        def get(self) -> list[Response | Effect]:
            ...
    ```
#####  Custom 
It's also possible to create custom authentication schemes. There are two ways to do this.
The first way is to access authentication headers on the request object directly. If you wish to do this, then you would define your authenticate method to take a `Credentials` object, and pull the authentication values from the request headers:
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.simple_api import Response
    from canvas_sdk.handlers.simple_api import Credentials, SimpleAPIRoute
    class MyAPI(SimpleAPIRoute):
        PATH = "/routes/hello-world"
        def authenticate(self, credentials: Credentials) -> bool:
            provided_api_key = self.request.headers["My-API-Key"]
            provided_app_key = self.request.headers["My-App-Key"]
            # Validate provided credentials against the credentials in self.secrets
            ...
        def get(self) -> list[Response | Effect]:
            ...
    ```
Another way to do this is by defining your own `Credentials` subclass which obtains the authentication values out of the request headers:
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.simple_api import Response
    from canvas_sdk.handlers.simple_api import Credentials, SimpleAPIRoute
    from canvas_sdk.handlers.simple_api.api import Request
    class MyCredentials(Credentials):
        def __init__(self, request: Request) -> None:
            self.api_key = self.request.headers['My-API-Key']
            self.app_key = self.request.headers['My-App-Key']
    class MyAPI(SimpleAPIRoute):
        PATH = "/routes/hello-world"
        def authenticate(self, credentials: MyCredentials) -> bool:
            provided_api_key = credentials.api_key
            provided_app_key = credentials.app_key
            # Validate provided credentials against the credentials in self.secrets
            ...
        def get(self) -> list[Response | Effect]:
            ...
    ```
####  Authentication mixins 
The Canvas SDK offers several "batteries included" authentication mixins that you can use to implement your authentication method. If you choose to use these, then the only action you must take is to ensure that you set the appropriate secrets for your plugin on your instance.
Make sure you always list the mixin class to the left of the base class, which is **SimpleAPIRoute** in the examples below.
#####  Basic 
If you want an implementation of Basic authentication, you can use the `BasicAuthMixin`. You will need to declare the `simpleapi-basic-username` and `simpleapi-basic-password` secrets in your manifest file, and then set the secrets on your instance after you deploy your plugin.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.simple_api import JSONResponse, Response
    from canvas_sdk.handlers.simple_api import BasicAuthMixin, SimpleAPIRoute
    class MyAPI(BasicAuthMixin, SimpleAPIRoute):
        PATH = "/routes/hello-world"
        def get(self) -> list[Response | Effect]:
            return [
                JSONResponse({"message": "Hello world!"})
            ]
    ```
#####  API key 
If you want an implementation of API key authentication, you can use the `APIKeyAuthMixin`. You will need to declare the `simpleapi-api-key` secret in your manifest file, and then set the secret on your instance after you deploy your plugin.
You can generate a secure, random API key like this:
    ```shell
    python -c "import secrets; print(secrets.token_hex(16))"
    ```
Copy the output from that command, and set the `simpleapi-api-key` secret on your instance.
After you set your secret, you can use the `APIKeyAuthMixin`:
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.simple_api import JSONResponse, Response
    from canvas_sdk.handlers.simple_api import APIKeyAuthMixin, SimpleAPIRoute
    class MyAPI(APIKeyAuthMixin, SimpleAPIRoute):
        PATH = "/routes/hello-world"
        def get(self) -> list[Response | Effect]:
            return [
                JSONResponse({"message": "Hello world!"})
            ]
    ```
#####  Staff Session 
If you want to ensure the visiting user is a logged in staff user, you can use the `StaffSessionAuthMixin`. This makes no assertions about the particular staff member, just that they are staff, and that they are logged in.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.simple_api import JSONResponse, Response
    from canvas_sdk.handlers.simple_api import StaffSessionAuthMixin, SimpleAPIRoute
    class MyAPI(StaffSessionAuthMixin, SimpleAPIRoute):
        PATH = "/routes/hello-world"
        def get(self) -> list[Response | Effect]:
            return [
                JSONResponse({"message": "Hello world!"})
            ]
    ```
#####  Patient Session 
If you want to ensure the visiting user is a logged in patient user, you can use the `PatientSessionAuthMixin`. This makes no assertions about the particular patient, just that they are a patient, and that they are logged in.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.simple_api import JSONResponse, Response
    from canvas_sdk.handlers.simple_api import PatientSessionAuthMixin, SimpleAPIRoute
    class MyAPI(PatientSessionAuthMixin, SimpleAPIRoute):
        PATH = "/routes/hello-world"
        def get(self) -> list[Response | Effect]:
            return [
                JSONResponse({"message": "Hello world!"})
            ]
    ```
##  Acting as a Canvas user 
By default, a SimpleAPI request isn't tied to a specific person, so any effects it returns — such as creating, locking, or signing a note — are recorded as Canvas Bot rather than a clinician.
To have a request run **as a specific Canvas staff member** — for example, so a note is signed under the treating provider's name — call the endpoint with an access token obtained through the [Authorization Code flow](/api/customer-authentication#authorization-code). That flow issues a token that represents the staff member who signed in and approved it. Send it as a Bearer token in the `Authorization` header, and Canvas identifies the user from the token and treats the request as coming from them, so any effects the handler returns are attributed to that staff member.
    ```bash
    curl --request POST \
      --url 'https://example.canvasmedical.com/plugin-io/api/my_plugin/note/<note-id>/sign' \
      --header 'Authorization: Bearer <access-token>'
    ```
In this example the note is signed and recorded in Canvas as signed by the staff member who authorized the access token, not by Canvas Bot.
----- END PAGE https://docs.canvasmedical.com/sdk/handlers-simple-api-http/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/handlers-simple-api-websocket/
WebSocket APIs in Canvas let you define **channels** that clients can connect to. These APIs support one-way, server-to-client communication, and are designed for use cases such as real-time notifications.
To use websockets in a Canvas plugin, you must define the WebSocketAPI auth handler to manage access to your channel, use the `Broadcast` effect to publish messages to that channel, and create client code to manage the connection, handle new messages, and gracefully reconnect.
###  Defining a WebSocket API 
To define a WebSocket handler, subclass `WebSocketAPI`. You must implement an `authenticate` method that determines whether the connection should be accepted.
    ```python
    from canvas_sdk.handlers.simple_api.websocket import WebSocketAPI
    class MyWebSocketAPI(WebSocketAPI):
        def authenticate(self) -> bool:
            ...
    ```
If `authenticate()` returns `True`, the connection is accepted. Otherwise, it is denied.
Clients should connect using a URL that maps to your plugin and channel name:
    wss://<instance>.canvasmedical.com/plugin-io/ws/<plugin_name>/<channel_name>/
###  WebSocket Object 
When a handler is invoked, the `websocket` object is available as an attribute on the handler. This object provides details about the connection, including:
  - `channel`: The channel name from the connection URL
  - `headers`: A dictionary of headers associated with the connection
  - `api_key`: The api key, if present
  - `logged_in_user`: A dictionary like `{ "id": ..., "type": ... }` if a logged-in user is present
You can access this object within your handler methods using `self.websocket`.
> **Info:** Channel names may only contain alphanumeric, hyphen, and underscore characters. 
###  Authentication 
You must implement the `authenticate()` method in your handler class. Two authentication methods are supported:
####  Session-Based (Internal Clients) 
For connections initiated from within the Canvas browser UI by a logged-in user:
    ```python
    def authenticate(self) -> bool:
        logged_in_user = self.websocket.logged_in_user
        # Structure looks like:
        # {
        #     "id": "abc123",
        #     "type": "Staff"
        # }
        # Where "type" is "Staff" or "Patient"
        # You could authenticate based on type or check to see if the
        # individual is in a particular group or team.
        ...
    ```
####  APIKey-Based (External Clients) 
For external tools or scripts, pass an auth key as a query parameter:
    wss://<instance>.canvasmedical.com/plugin-io/ws/<plugin>/<channel>?api_key=<key>
The key is made available in your handler via `self.websocket.api_key`. You can store this key as a secret.
    ```python
    def authenticate(self) -> bool:
      provided_token = self.websocket.api_key
      # Validate provided key against an API key in self.secrets
      ...
    ```
###  Broadcasting Messages 
To send a message to all connected clients on a WebSocket channel, use the `Broadcast` effect. This effect can be returned from any handler.
The `Broadcast` effect takes the following parameters:
  - `message`: A JSON-serializable Python dictionary or value.
  - `channel`: The target channel name as a string.
Here's an example using a SimpleAPI HTTP POST route to trigger a broadcast:
    ```python
    from http import HTTPStatus
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.simple_api import Broadcast, JSONResponse, Response
    from canvas_sdk.handlers.simple_api import Credentials, SimpleAPI, api
    class WebhookAPI(SimpleAPI):
        @api.post("/callback")
        def broadcast_message(self) -> list[Response | Effect]:
            body = self.request.json()
            return [
                Broadcast(message=body, channel="notifications").apply(),
                JSONResponse({"status": "ok"}, status_code=HTTPStatus.ACCEPTED),
            ]
    ```
In this example, when the `/callback` endpoint receives a POST request, it broadcasts the request body as a message to all clients subscribed to the `notifications` channel.
----- END PAGE https://docs.canvasmedical.com/sdk/handlers-simple-api-websocket/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/handlers-simple-api/
The Canvas SDK allows developers to define both HTTP and WebSocket APIs to integrate custom plugin logic into a Canvas instance.
[ HTTP API Define endpoints to handle incoming HTTP requests using custom logic and authentication. ](/sdk/handlers-simple-api-http) [ WebSocket API Establish real-time communication channels for interactive plugin behavior and updates. ](/sdk/handlers-simple-api-websocket) [ Commands API Expose commands as HTTP endpoints, validated against the command itself. ](/sdk/handlers-simple-api-commands)
----- END PAGE https://docs.canvasmedical.com/sdk/handlers-simple-api/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/handlers/
The handlers module lets you define reactions to events.
Handlers respond to [Events](/sdk/events/) and return zero, one, or many [Effects](/sdk/effects/).
There are some special types of handlers, like [Protocols](/sdk/protocols/) and [CronTasks](/sdk/handlers-crontask/). These offer a differentiated interface for their particular use-cases. For example, CronTasks only ever respond to the `CRON` event, require a schedule to be specified, and expect the `execute` method to be implemented rather than `compute`.
All handlers inherit from [BaseHandler](/sdk/handlers-basehandler/), which means you always have access to event and configuration data with the following accessors:
  - `self.event`
    - Information about the event including the event type
  - `self.context`
    - Data included as the event payload
  - `self.target`
    - Identifying information to help you reference the subject of the event
  - `self.secrets`
    - Configuration key-value store for your plugin
    - Secrets have their keys defined in the `CANVAS_MANIFEST.json` and their values are set by the Canvas instance administrator after installing your plugin via the Canvas UI on your plugin's configuration page
  - `self.environment`
    - Information about the Canvas instance your plugin is being executed on.
    - Available keys: 
      - `CUSTOMER_IDENTIFIER` — the instance's subdomain (e.g. `acme` for `acme.canvasmedical.com`).
      - `INSTALLATION_TIME_ZONE` — the instance's configured time zone as an IANA name (e.g. `America/Los_Angeles`). Useful for rendering times to the customer's clinicians, scheduling work in their local day, or formatting dates in user-facing output.
    - Example: `self.environment['CUSTOMER_IDENTIFIER']`
## [ Action Button  Add a button that executes your custom code when clicked. ![Abridged source code of an action button implementation.](/assets/images/sdk/handlers/ActionButton.png) ![Image of an action button in a note header.](/assets/images/sdk/handlers/action-button-in-action.png) ](/sdk/handlers-action-buttons/) ## [ Application  Launch an iframe when your icon is clicked in the app drawer. ![Abridged source code of an application implementation.](/assets/images/sdk/handlers/Application-cropped.png) ![Image of application icons in the app drawer.](/assets/images/sdk/handlers/application-applied.png) ](/sdk/handlers-applications/) ## [ Cron Task  Execute your code on a cron-like schedule. ![Abridged source code of an action button implementation.](/assets/images/sdk/handlers/CronTask-cropped.png) ![Picture of Mr. Cron, the little time monster that remembers which tasks should be executed at any given time.](/assets/images/sdk/handlers/mr-cron.png) ](/sdk/handlers-crontask/) ## [ Base Handler  Respond to events with your custom code. ![Abridged source code of a base handler implementation.](/assets/images/sdk/handlers/BaseHandler-cropped.png) ![Stylized text that reads 'When X occurs, under Y conditions, I want Z to happen'.](/assets/images/sdk/handlers/base-handler-can-lend-a-hand.png) ](/sdk/handlers-basehandler/) ## [ Payment Processor  Integrate a third-party payment provider to charge cards and manage saved payment methods. ![Abridged source code of a payment processor implementation.](/assets/images/sdk/handlers/PaymentProcessor-cropped.png) ![Illustration of a credit card payment being approved.](/assets/images/sdk/handlers/payment-processor-in-action.png) ](/sdk/handlers-payment-processors/) ## [ Embedded Application  Render a tab inside a note, or replace the built-in scheduling modal. ![Abridged source code of a NoteApplication and SchedulingApplication implementation.](/assets/images/sdk/handlers/EmbeddedApplication-cropped.png) ![An embedded application rendered inside the Canvas UI.](/assets/images/sdk/handlers/embedded-application-in-action.png) ](/sdk/handlers-embedded-applications/) ## [ Patient Chart Summary Custom Section  Serve content into a custom section of the patient chart summary. ![Abridged source code of a patient chart summary custom section handler implementation.](/assets/images/sdk/handlers/PatientChartSummaryCustomSection-cropped.png) ![A custom section rendered in the patient chart summary.](/assets/images/sdk/handlers/patient-chart-summary-custom-section-handler-in-action.png) ](/sdk/patient-chart-summary-custom-section-handler/) ## [ SimpleAPI  Define HTTP and WebSocket API endpoints in your plugin. ![Abridged source code of a SimpleAPI route implementation.](/assets/images/sdk/handlers/SimpleAPI-cropped.png) ![A curl request to a SimpleAPI route and its JSON response.](/assets/images/sdk/handlers/simple-api-response.png) ](/sdk/handlers-simple-api/)
----- END PAGE https://docs.canvasmedical.com/sdk/handlers/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/layout-effect/
##  Patient Summary 
There are many summary sections in a patient's chart, organized by data type. While there is a default ordering, you can use an Effect to reorder them or hide some of them entirely. The `PatientChartSummaryConfiguration` class helps you craft the effect to do so.
![Before and after](/assets/images/sdk/summary-section-modified.png)
The example below shows reordering and hiding or omitting some of the sections:
    ```python
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers.base import BaseHandler
    from canvas_sdk.effects.patient_chart_summary_configuration import PatientChartSummaryConfiguration
    class SummarySectionLayout(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.PATIENT_CHART_SUMMARY__SECTION_CONFIGURATION)
        def compute(self):
            layout = PatientChartSummaryConfiguration(sections=[
              PatientChartSummaryConfiguration.Section.CARE_TEAMS,
              PatientChartSummaryConfiguration.Section.SOCIAL_DETERMINANTS,
              PatientChartSummaryConfiguration.Section.ALLERGIES,
              PatientChartSummaryConfiguration.Section.CONDITIONS,
              PatientChartSummaryConfiguration.Section.MEDICATIONS,
              PatientChartSummaryConfiguration.Section.VITALS,
            ])
            return [layout.apply()]
    ```
The `PatientChartSummaryConfiguration` takes a single argument, `sections`, which is expected to be a list at least one element long, filled with choices from the `PatientChartSummaryConfiguration.Section` enum. The `.apply()` method returns a well-formed `Effect` object.
This effect is only used in response to the `PATIENT_CHART_SUMMARY__SECTION_CONFIGURATION` event. It does nothing in any other context.
Values in the `PatientChartSummaryConfiguration.Section` enum are:
Constant | Description  
---|---  
SOCIAL_DETERMINANTS | social_determinants  
GOALS | goals  
CONDITIONS | conditions  
MEDICATIONS | medications  
ALLERGIES | allergies  
CARE_TEAMS | care_teams  
VITALS | vitals  
IMMUNIZATIONS | immunizations  
SURGICAL_HISTORY | surgical_history  
FAMILY_HISTORY | family_history  
CODING_GAPS | coding_gaps  
###  Custom Sections 
In addition to the built-in sections above, you can add fully custom sections to the chart summary. Custom sections render plugin-provided content in an iframe and are identified by a unique key. See [Patient Chart Summary Custom Section Handler](/sdk/patient-chart-summary-custom-section-handler/) for details on how to implement one.
###  Action Buttons 
Each section of the patient chart can also be customized with action buttons. Please refer to the [Action Buttons](/sdk/handlers-action-buttons/) documentation for more information.
##  Patient Profile 
The `PatientProfileConfiguration` class allows you to reorder, hide, and/or specificy whether sections load expanded or collapsed.
    ```python
    import json
    from canvas_sdk.effects import Effect, EffectType
    from canvas_sdk.effects.patient_profile_configuration import PatientProfileConfiguration
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from logger import log
    class MyHandler(BaseHandler):
        """This protocol is used to configure which sections appear in the Patient Profile section.
        The SHOW_PATIENT_PROFILE_SECTIONS payload expects a list of sections where each section is a dict like { "type": str, "start_expanded": bool }
        The accepted values for the "type" are:
        "demographics", "preferences", "preferred_pharmacies", "patient_consents",
        "care_team", "parent_guardian", "addresses", "phone_numbers", "emails", "contacts"
        """
        # Name the event type you wish to run in response to
        RESPONDS_TO = EventType.Name(EventType.PATIENT_PROFILE__SECTION_CONFIGURATION)
        def compute(self) -> list[Effect]:
            """This method gets called when an event of the type RESPONDS_TO is fired."""
            sections = [
                PatientProfileConfiguration.Payload(type=PatientProfileConfiguration.Section.PREFERENCES,
                                                                 start_expanded=False),
                PatientProfileConfiguration.Payload(type=PatientProfileConfiguration.Section.DEMOGRAPHICS,
                                                                 start_expanded=False),
                PatientProfileConfiguration.Payload(
                    type=PatientProfileConfiguration.Section.PREFERRED_PHARMACIES, start_expanded=True),
                PatientProfileConfiguration.Payload(type=PatientProfileConfiguration.Section.PARENT_GUARDIAN,
                                                                 start_expanded=False),
                PatientProfileConfiguration.Payload(type=PatientProfileConfiguration.Section.CONTACTS,
                                                    start_expanded=True),
                PatientProfileConfiguration.Payload(type=PatientProfileConfiguration.Section.CARE_TEAM,
                                                                 start_expanded=False),
                PatientProfileConfiguration.Payload(type=PatientProfileConfiguration.Section.TELECOM,
                                                                 start_expanded=False),
                PatientProfileConfiguration.Payload(type=PatientProfileConfiguration.Section.ADDRESSES,
                                                                 start_expanded=False),
                PatientProfileConfiguration.Payload(type=PatientProfileConfiguration.Section.PATIENT_CONSENTS,
                                                    start_expanded=False),
            ]
            effect = PatientProfileConfiguration(sections=sections).apply()
            return [effect]
    ```
The `PatientProfileConfiguration` takes a single argument, `sections`, which is expected to be a list at least one element long, filled with `PatientProfileConfiguration.Payload` objects. These are python typed dictionaries that expect a `PatientProfileConfiguration.Section` choice, which describes a section of the patient profile, and a `start_expanded` boolean, which determines if the fields in that section should be exposed by default. The `.apply()` method returns a well-formed `Effect` object.
This effect is only used in response to the `PATIENT_PROFILE__SECTION_CONFIGURATION` event. It does nothing in any other context.
Values in the `PatientProfileConfiguration.Section` enum are:
Constant | Description  
---|---  
DEMOGRAPHICS | demographics  
PREFERENCES | preferences  
PREFERRED_PHARMACIES | preferred_pharmacies  
PATIENT_CONSENTS | patient_consents  
CARE_TEAM | care_team  
PARENT_GUARDIAN | parent_guardian  
ADDRESSES | addresses  
TELECOM | telecom  
CONTACTS | contacts  
##  Panel Configuration 
This effect allows you to define which panel buttons should be displayed on the main page or the patient page.
The order of the buttons in the array will determine their order on the panel.
![Before and after](/assets/images/sdk/panel-configuration-before-after.png)(width:70%)
    ```python
    from canvas_sdk.effects.panel_configuration import PanelConfiguration
    PanelConfiguration(
      sections=[
        PanelConfiguration.PanelPatientSection.REFILL_REQUEST,
        PanelConfiguration.PanelPatientSection.LAB_REPORT,
        PanelConfiguration.PanelPatientSection.CHANGE_REQUEST,
        PanelConfiguration.PanelPatientSection.TASK,
    ], page=PanelConfiguration.Page.PATIENT).apply()
    ```
A PanelConfiguration effect consists of the following properties:
###  Attributes 
Attribute | Type | Description  
---|---|---  
`sections` | `list[PanelPatientSection] or list[PanelGlobalSection]` | list of section items.  
`page` | `Page` | PATIENT or GLOBAL.  
Values in the `PanelGlobalSection` enum are:
Constant | Description  
---|---  
APPOINTMENT | appointment  
CHANGE_REQUEST | changeRequest  
IMAGING_REPORT | imagingReport  
INPATIENT_STAY | inpatientStay  
LAB_REPORT | labReport  
MESSAGE | message  
OUTSTANDING_REFERRAL | outstandingReferral  
PRESCRIPTION_ALERT | prescriptionAlert  
RECALL_APPOINTMENT | recallAppointment  
REFERRAL_REPORT | referralReport  
REFILL_REQUEST | refillRequest  
TASK | task  
UNCATEGORIZED_DOCUMENT | uncategorizedDocument  
Values in the `PanelPatientSection` enum are:
Constant | Description  
---|---  
CHANGE_REQUEST | changeRequest  
COMMAND | command  
IMAGING_REPORT | imagingReport  
INPATIENT_STAY | inpatientStay  
LAB_REPORT | labReport  
PRESCRIPTION_ALERT | prescriptionAlert  
REFERRAL_REPORT | referralReport  
REFILL_REQUEST | refillRequest  
TASK | task  
UNCATEGORIZED_DOCUMENT | uncategorizedDocument  
##  Patient Note Header Dropdown Configuration 
The `PatientNoteHeaderDropdownConfiguration` effect allows you to define which items appear in the dropdown menu on a patient's note header (the triple dots at the top right of each note).
The order in the dropdown is preserved and grouped into specific sections, rather than being based on the plugin item order.
![Before and after](/assets/images/sdk/note-header-configuration.png)(width:60%)
    ```python
    from canvas_sdk.effects.patient_note_header_dropdown_configuration import PatientNoteHeaderDropdownConfiguration
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.effects import Effect
    class NoteHeaderDropdownHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.PATIENT_NOTE_HEADER_DROPDOWN__SECTION_CONFIGURATION)
        def compute(self) -> list[Effect]:
            return [PatientNoteHeaderDropdownConfiguration(items=[
                PatientNoteHeaderDropdownConfiguration.Items.PRINT_NOTE,
                PatientNoteHeaderDropdownConfiguration.Items.PRINT_SUPERBILL,
                PatientNoteHeaderDropdownConfiguration.Items.LINK_TO_PHONE,
            ]).apply()]
    ```
###  Attributes 
Attribute | Type | Description  
---|---|---  
`items` | `list[Items]` | List of dropdown items to display.  
Values in the `PatientNoteHeaderDropdownConfiguration.Items` enum are:
Constant | Description  
---|---  
LINK_TO_PHONE | Show QR code to link mobile device to note  
SOAP | Sort note sections in SOAP order (Subjective, Objective, Assessment, Plan)  
APSO | Sort note sections in APSO order (Assessment, Plan, Subjective, Objective)  
CHANGE_LOCATION | Change the note's practice location  
CHANGE_PROVIDER | Change the note's provider  
CHANGE_DATE_OF_SERVICE | Change the note's date of service  
PRINT_SUPERBILL | Print the superbill for billing  
PRINT_ROOMING_SHEET | Print the rooming sheet for care team  
PRINT_AFTER_VISIT_SUMMARY | Print the patient after visit summary  
COPY_LINK | Copy the note's permalink to clipboard  
PRINT_NOTE | Print the note for care team  
FAX_NOTE | Fax the note to an external recipient  
FAX_EVENT_HISTORY | View fax event history for the note  
MOVE_COMMANDS | Move commands from this note to another note  
##  Provider Menu Configuration 
The `ProviderMenuConfiguration` effect allows you to define which items appear in the provider menu (the hamburger menu at the top left of Canvas).
The effect replaces the default set of items, so every item that should stay visible has to be listed. Anything you omit is not rendered. If no installed plugin emits the effect, the menu renders unchanged.
Passing an empty list is allowed and hides every native item — useful if your plugin replaces the menu entirely with its own items.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.provider_menu_configuration import ProviderMenuConfiguration
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class ProviderMenuHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.GET_PROVIDER_MENU_CONFIGURATION)
        def compute(self) -> list[Effect]:
            return [ProviderMenuConfiguration(items=[
                ProviderMenuConfiguration.Items.PATIENTS,
                ProviderMenuConfiguration.Items.CAMPAIGNS,
                ProviderMenuConfiguration.Items.SETTINGS,
            ]).apply()]
    ```
Three things the effect does not do:
  - **It does not reorder the menu.** Items render in Canvas's native order and grouping, regardless of the order you list them in.
  - **It does not grant access.** Permissions still apply on top, so an item you list will still render disabled for a user who lacks the permission for it.
  - **It does not affect plugin-provided menu items.** Applications with the `provider_menu_item` scope are independent of the allow-list.
The user's avatar and name, and the **Sign out** button, are always rendered and cannot be hidden.
Because this is an allow-list rather than a block-list, it does not pick up native items added in future Canvas releases. If a new item ships and you want it visible, add it to your list — otherwise it stays hidden on your instance.
####  When the allow-list is not applied 
Canvas falls back to rendering every native item, rather than a partial or empty menu, in each of these cases:
  - No installed plugin responds to the event.
  - The plugin raises while resolving the configuration.
  - The allow-list reaches Canvas containing an item it does not recognize — the whole list is discarded, not just the unrecognized entry.
Passing something that is not an `Items` member raises a validation error when you construct `ProviderMenuConfiguration`, so most mistakes surface in your plugin before they ever reach Canvas.
If more than one installed plugin responds with a `ProviderMenuConfiguration`, the last effect Canvas receives wins — its allow-list replaces the earlier ones rather than merging with them.
###  Attributes 
Attribute | Type | Description  
---|---|---  
`items` | `list[Items]` | List of menu items to display.  
Values in the `ProviderMenuConfiguration.Items` enum are:
Constant | Description  
---|---  
SCHEDULE | Go to the schedule page  
PATIENTS | Go to the patient directory  
REVENUE | Go to the revenue page  
POPULATIONS | Go to the populations page  
CAMPAIGNS | Go to the campaigns page  
DATA_INTEGRATION | Go to the data integration queue  
QUESTIONNAIRE_BUILDER | Go to the questionnaire builder  
SETTINGS | Open the Canvas admin site in a new tab  
MULTI_FACTOR_AUTHENTICATION | Open multi-factor authentication setup in a new tab  
CHANGELOG | Open the Canvas release notes in a new tab  
HELP_CENTER | Open the Canvas help center in a new tab  
###  Hiding the Schedule item 
Hiding `SCHEDULE` does not change where providers land after logging in — that still defaults to the schedule page. Pair the effect with a [`DefaultHomepageEffect`](/sdk/default-homepage-effect/) so providers do not arrive on a page they can no longer navigate back to.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.default_homepage import DefaultHomepageEffect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class ScheduleFreeHomepage(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.GET_HOMEPAGE_CONFIGURATION)
        def compute(self) -> list[Effect]:
            return [DefaultHomepageEffect(page=DefaultHomepageEffect.Pages.PATIENTS).apply()]
    ```
Hiding `SCHEDULE` also leaves the Appointments filter in the side panel in place. Removing the scheduling experience end to end means coordinating three independent controls: this effect for the menu item, `PanelConfiguration` for the Appointments filter, and [`DefaultHomepageEffect`](/sdk/default-homepage-effect/) for the landing page.
Omitting `SETTINGS` or `MULTI_FACTOR_AUTHENTICATION` hides the links to the admin site and to multi-factor authentication setup, so make sure your users have another route to them if they need one.
##  Modals 
The `LaunchModalEffect` class allows you to launch modals in Canvas, providing a flexible way to display content or navigate to external resources.
###  Example Usage 
    ```python
    from canvas_sdk.effects.launch_modal import LaunchModalEffect
    class ModalEffectHandler:
        def compute(self):
            modal_effect = LaunchModalEffect(
                url="https://example.com/info",
                content=None,
                target=LaunchModalEffect.TargetType.DEFAULT_MODAL,
                title="Example Info"
            )
            return [modal_effect.apply()]
    ```
The `LaunchModalEffect` class has the following properties:
  - **url** : A string containing the URL to load within the modal. If `content` is also specified, an error will be raised.
  - **content** : A string containing the content to be displayed directly within the modal. If `url` is also provided, an error will be raised.
  - **target** : Defines where the modal should be launched. Options include: 
    - `DEFAULT_MODAL`: Opens the URL in a modal centered on the screen.
    - `NEW_WINDOW`: Opens the content in a new browser window.
    - `RIGHT_CHART_PANE`: Opens the URL in the right-hand pane of the patient chart.
    - `RIGHT_CHART_PANE_LARGE`: Like above, but a bit wider.
    - `PAGE`: Opens the content as a full page.
    - `NOTE`: Opens the content within a note tab (used with Note Applications).
    - `DOCKED_PANE`: Opens the content in a persistent pane pinned to an edge of the window. This target is returned by a [Docked Application](/sdk/handlers-embedded-applications/#docked-applications), which sets `DOCK_EDGE` and `DOCK_SIZE`.
  - **title** : A string containing the title of the modal and will be displayed when minimized. Defaults to `Untitled`
###  Closing Modals from Applications 
When building applications with the Canvas SDK, you may encounter scenarios where you need to programmatically dismiss modals. This can be particularly useful in automated testing or when creating user flows that require closing modals based on certain conditions.
Here's a simple example of how to dismiss modals from your applications using JavaScript.
    ```html
    <script>
        let messagePort = null;
        // Listen for the port transfer from the Canvas Application
        window.addEventListener('message', (event) => {
          // Check if this is the INIT_CHANNEL message with a port
          if (event.data?.type === 'INIT_CHANNEL' && event.ports[0]) {
            // Store the port for later use
            messagePort = event.ports[0];
            messagePort.start();
            messagePort.postMessage({ type: 'CLOSE_MODAL' });
          }
        });
    </script>
    ```
And that's it! This script establishes a communication channel with the Canvas Application by listening for the `INIT_CHANNEL` event, capturing the message port, and then sending a `CLOSE_MODAL` message through that port to close any open modals when the application loads. You can customize the event listener to trigger the modal dismissal based on your specific requirements.
While developers might find odd to be sending a message to themselves, this is the current method supported by the Canvas SDK for dismissing modals, in order to avoid potential security issues with cross-origin messaging and flooding the main application with messages.
This twist on the _Holywood Principle_ ensures that your application remains secure while still providing the functionality needed to manage modals effectively.
##  Resizing Modals 
Modal overlays can now be dynamically resized by embedded applications using the MessageChannel API. Applications launching with a `DEFAULT_MODAL` target can send a `RESIZE` message to adjust the modal's width and/or height:
    ```html
    <script>
        let messagePort = null;
        // Listen for the port transfer from the Canvas Application
        window.addEventListener('message', (event) => {
          // Check if this is the INIT_CHANNEL message with a port
          if (event.data?.type === 'INIT_CHANNEL' && event.ports?.[0]) {
            // Store the port for later use
            messagePort = event.ports[0];
            messagePort.start();
            // Example: Resize modal to specific dimensions
            messagePort.postMessage({
              type: 'RESIZE',
              width: 800,  // pixels
              height: 600  // pixels
            });
          }
        });
    </script>
    ```
This enables embedded applications to optimize their display area based on content requirements, improving the user experience for dynamic or responsive plugin interfaces.
##  Custom HTML and Django Templates 
To facilitate the use of custom HTML, you can utilize the `render_to_string` utility from `canvas_sdk.templates` to render Django templates with a specified context. This allows for dynamic rendering of HTML that can be passed to a `LaunchModalEffect` or `PortalWidget`.
    ```python
    from typing import Any
    def render_to_string(template_name: str, context: dict[str, Any] | None = None) -> str | None:
        """Load a template and render it with the given context.
        Args:
            template_name (str): The path to the template file, relative to the plugin package.
                If the path starts with a forward slash ("/"), it will be stripped during resolution.
            context (dict[str, Any] | None): A dictionary of variables to pass to the template
                for rendering. Defaults to None, which uses an empty context.
        Returns:
            str: The rendered template as a string.
        Raises:
            FileNotFoundError: If the template file does not exist within the plugin's directory
                or if the resolved path is invalid.
        """
    ```
####  Example Template 
Consider a simple HTML file named `templates/custom_content.html`:
    ```html
    <!DOCTYPE html>
    <html>
      <head>
        <title>{{ title }}</title>
      </head>
      <body>
        <h1>{{ heading }}</h1>
        <p>{{ message }}</p>
      </body>
    </html>
    ```
This template uses Django template placeholders like `{{ title }}`, `{{ heading }}`, and `{{ message }}` to dynamically render content based on the provided context.
####  Rendering the Template in Python 
Here's how you can use the `render_to_string` utility to render the template and pass the resulting HTML to a `LaunchModalEffect` or `PortalWidget`:
    ```python
    from canvas_sdk.effects.launch_modal import LaunchModalEffect
    from canvas_sdk.effects.widgets import PortalWidget
    from canvas_sdk.templates import render_to_string
    class ModalEffectHandler:
        def compute(self):
            # Define the context for the template
            context = {
                "title": "Welcome Modal",
                "heading": "Hello, User!",
                "message": "This is a dynamically rendered modal using Django templates."
            }
            # Render the HTML content using the template and context
            rendered_html = render_to_string("templates/custom_content.html", context)
            # Create a LaunchModalEffect with the rendered content
            modal_effect = LaunchModalEffect(
                content=rendered_html,
                target=LaunchModalEffect.TargetType.DEFAULT_MODAL
            )
            return [modal_effect.apply()]
    class PortalWidgetHandler:
        def compute(self):
            # Define the context for the template
            context = {
                "title": "Welcome Modal",
                "heading": "Hello, User!",
                "message": "This is a dynamically rendered modal using Django templates."
            }
            # Render the HTML content using the template and context
            rendered_html = render_to_string("templates/custom_content.html", context)
            # Create a PortalWidget with the rendered content
            portal_widget = PortalWidget(
                content=rendered_html,
                size=PortalWidget.Size.COMPACT,
                priority=25
            )
            return [portal_widget.apply()]
    ```
##  Additional Configuration 
To use URLs or custom scripts within the `LaunchModalEffect` or `PortalWidget`, additional security configurations must be specified in the `CANVAS_MANIFEST.json` file of your plugin.
  - **Allowing URLs** : URLs specified in the **url** property must be added to the `url_permissions` section of the `CANVAS_MANIFEST.json` in order for the URL to load properly.
  - **Allowing custom scripts** : If you need to load scripts from an external source, the URL for the script must be added to the `url_permissions` section of the `CANVAS_MANIFEST.json` and `'SCRIPTS'` must be in the permissions list.
  - **Requesting microphone access** : If the site in your modal or widget needs microphone access, `'MICROPHONE'` must be in the URL's permissions list.
  - **Requesting camera access** : If the site in your modal or widget needs camera access, `'CAMERA'` must be in the URL's permissions list.
  - **Requesting clipboard read access** : If the site in your modal or widget needs to read from the user's clipboard, `'CLIPBOARD_READ'` must be in the URL's permissions list.
  - **Requesting clipboard write access** : If the site in your modal or widget needs to write to the user's clipboard, `'CLIPBOARD_WRITE'` must be in the URL's permissions list.
  - **Allowing browser access to cookies from the iframe's origin** : If you want the loaded URL to access cookies for its domain, `'ALLOW_SAME_ORIGIN'` must be in the URL's permissions list. If the URL you're loading requires authentication, this will prevent your user from having to log in each time the modal is launched.
The URLs must match the format available [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy#host-source).
    ```json
    {
      "sdk_version": "0.1.4",
      "plugin_version": "0.0.1",
      "name": "custom_html",
      "description": "...",
      "url_permissions": [
        {
          "url": "https://example.com/info",
          "permissions": ["ALLOW_SAME_ORIGIN", "MICROPHONE", "CAMERA", "CLIPBOARD_READ", "CLIPBOARD_WRITE"]
        },
        {
          "url": "https://d3js.org/d3.v4.js",
          "permissions": ["SCRIPTS"]
        }
      ]
    }
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/layout-effect/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/patient-chart-group-effect/
##  Overview 
This effect allows developers to group items in a patient chart section. You can define multiple groups with a name, priority, and the items that belong to each group.
Currently, this is supported for the Conditions, Medications, and Detected Issues sections.
    ```python
    from canvas_sdk.effects.patient_chart_group import PatientChartGroup
    from canvas_sdk.effects.group import Group
    conditions = [{
        "id": 1,
        "codings": {
          "code": "111",
          "system": "ICD-10",
          "display": "Ophiasis",
        }
      }, {
        "id": 2,
        "codings": {
          "code": "112",
          "system": "ICD-10",
          "display": "Acute angle-closure glaucoma",
        }
    }]
    PatientChartGroup(items=[
      {
        "Psychiatry": Group(priority=100, items=conditions, name="Psychiatry")
      },
      {
        "General": Group(priority=200, items=conditions, name="General")
      }
    ])
    ```
##  Structure 
###  **Group**
A Group consists of the following properties:
####  Attributes 
Attribute | Type | Description  
---|---|---  
`items` | `list` | list of items for each group, ex: [Condition] or [Medication]  
`priority` | `int` | the group's priority within the section.  
`name` | `str` | the group label.  
###  **PatientChartGroup**
A PatientChartGroup consists of the following properties:
####  Attributes 
Attribute | Type | Description  
---|---|---  
`items` | `dict[str, Group]` | list of Groups  
----- END PAGE https://docs.canvasmedical.com/sdk/patient-chart-group-effect/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/patient-chart-summary-custom-section-effect/
##  Overview 
The `PatientChartSummaryCustomSection` effect allows plugin developers to supply the content for a custom section in the patient chart summary. It is returned by a [`PatientChartSummaryCustomSectionHandler`](/sdk/patient-chart-summary-custom-section-handler/) in response to a request for the section's content.
Content can be provided as an inline HTML string or as a URL to a hosted page. An icon must always be supplied — it is shown in the chart summary header when the section is collapsed.
    ```python
    from canvas_sdk.effects.patient_chart_summary_custom_section import PatientChartSummaryCustomSection
    from canvas_sdk.templates import render_to_string
    # Serve content as an inline HTML string
    PatientChartSummaryCustomSection(
        content=render_to_string("templates/my_section.html"),
        icon="📋",
    )
    # Serve content from a hosted URL
    PatientChartSummaryCustomSection(
        url="/plugin-io/api/my_plugin/my-section",
        icon_url="/plugin-io/api/my_plugin/icon.png",
    )
    ```
##  Structure 
###  Attributes 
Attribute | Required | Type | Description  
---|---|---|---  
`content` | required (if `url` is not provided) | `str` | `None` | Inline HTML content to render in the section. Mutually exclusive with `url`.  
`url` | required (if `content` is not provided) | `str` | `None` | URL of the page to load in the section iframe. Mutually exclusive with `content`.  
`icon` | required (if `icon_url` is not provided) | `str` | `None` | Text or emoji displayed as the section icon when collapsed. Mutually exclusive with `icon_url`.  
`icon_url` | required (if `icon` is not provided) | `str` | `None` | URL of an image to use as the section icon when collapsed. Mutually exclusive with `icon`.  
###  Validation 
Exactly one of `content` / `url` must be provided, and exactly one of `icon` / `icon_url` must be provided. Providing both fields in a pair or neither will raise a `ValidationError` when `.apply()` is called.
##  Examples 
###  Inline HTML content 
Return a rendered HTML template as the section content.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.patient_chart_summary_custom_section import PatientChartSummaryCustomSection
    from canvas_sdk.handlers.patient_chart_summary_custom_section_handler import PatientChartSummaryCustomSectionHandler
    from canvas_sdk.templates import render_to_string
    class MySectionHandler(PatientChartSummaryCustomSectionHandler):
        SECTION_KEY = "my_section"
        def handle(self) -> list[Effect]:
            return [
                PatientChartSummaryCustomSection(
                    content=render_to_string("templates/my_section.html"),
                    icon="📋",
                ).apply()
            ]
    ```
###  URL-based content 
Load the section content from a hosted endpoint. This is useful when the section is a standalone page served by a [Simple API](/sdk/handlers-simple-api/) handler.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.patient_chart_summary_custom_section import PatientChartSummaryCustomSection
    from canvas_sdk.handlers.patient_chart_summary_custom_section_handler import PatientChartSummaryCustomSectionHandler
    class MySectionHandler(PatientChartSummaryCustomSectionHandler):
        SECTION_KEY = "my_section"
        def handle(self) -> list[Effect]:
            return [
                PatientChartSummaryCustomSection(
                    url="/plugin-io/api/my_plugin/my-section",
                    icon_url="/plugin-io/api/my_plugin/icon.png",
                ).apply()
            ]
    ```
###  Passing data to the template 
Use `render_to_string` with a context dictionary to inject dynamic data into the HTML template.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.patient_chart_summary_custom_section import PatientChartSummaryCustomSection
    from canvas_sdk.handlers.patient_chart_summary_custom_section_handler import PatientChartSummaryCustomSectionHandler
    from canvas_sdk.templates import render_to_string
    from canvas_sdk.v1.data.patient import Patient
    class MySectionHandler(PatientChartSummaryCustomSectionHandler):
        SECTION_KEY = "my_section"
        def handle(self) -> list[Effect]:
            patient = Patient.objects.get(id=self.target)
            return [
                PatientChartSummaryCustomSection(
                    content=render_to_string(
                        "templates/my_section.html",
                        {"patient_name": patient.first_name},
                    ),
                    icon="📋",
                ).apply()
            ]
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/patient-chart-summary-custom-section-effect/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/patient-chart-summary-custom-section-handler/
The `PatientChartSummaryCustomSectionHandler` is the base class for implementing custom sections in the patient chart summary. When Canvas needs to render a custom section, it fires an event that this handler intercepts, and the handler returns a [`PatientChartSummaryCustomSection`](/sdk/patient-chart-summary-custom-section-effect/) effect with the content to display.
##  Overview 
Custom chart summary sections are registered through [`PatientChartSummaryConfiguration`](/sdk/layout-effect/#patient-summary) and served by a `PatientChartSummaryCustomSectionHandler` subclass. Each handler is responsible for exactly one section, identified by its `SECTION_KEY`.
To implement a custom section you need two things:
  1. A `PatientChartSummaryConfiguration` handler that includes the section in the layout.
  2. A `PatientChartSummaryCustomSectionHandler` subclass that returns the section content.
Both handlers must be registered in `CANVAS_MANIFEST.json`.
##  Creating a Custom Section Handler 
Subclass `PatientChartSummaryCustomSectionHandler` and:
  1. Set `SECTION_KEY` to the unique identifier of your section. This must match the key used in `PatientChartSummaryConfiguration.CustomSection`.
  2. Implement `handle()` to return a `PatientChartSummaryCustomSection` effect.
  3. The patient id is available via `self.target`. Use it to scope database queries to the current patient.
  4. The logged-in user is available via `self.actor`. Use it to tailor the section content to the specific staff member viewing the chart.
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.patient_chart_summary_custom_section import PatientChartSummaryCustomSection
    from canvas_sdk.handlers.patient_chart_summary_custom_section_handler import PatientChartSummaryCustomSectionHandler
    from canvas_sdk.templates import render_to_string
    from canvas_sdk.v1.data.patient import Patient
    class MySectionHandler(PatientChartSummaryCustomSectionHandler):
        """Handles the PATIENT_CHART_SUMMARY__GET_CUSTOM_SECTION event for 'my_section'.
        Fetches the current patient and renders a template with their name.
        """
        SECTION_KEY = "my_section"
        def handle(self) -> list[Effect]:
            patient = Patient.objects.get(id=self.target)
            return [
                PatientChartSummaryCustomSection(
                    content=render_to_string("templates/my_section.html", { "patient_name": patient.full_name() }),
                    icon="📋",
                ).apply()
            ]
    ```
###  Required 
  - **`SECTION_KEY`** A unique string identifier for the section. Must match the key passed to `PatientChartSummaryConfiguration.CustomSection`. Omitting or leaving it empty will raise an `ImproperlyConfigured` error when the plugin loads.
  - **`handle()`** Called when Canvas requests the content for this section. Must return a list containing a single [`PatientChartSummaryCustomSection`](/sdk/patient-chart-summary-custom-section-effect/) effect.
##  Configuring Chart Summary Sections 
A custom section handler alone is not enough — the section must also be included in the chart summary layout. Use `PatientChartSummaryConfiguration` to declare which sections appear in the chart summary and in what order. For the full list of available built-in sections, see [Patient Summary layout effects](/sdk/layout-effect/#patient-summary).
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.patient_chart_summary_configuration import PatientChartSummaryConfiguration
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class MySummaryConfiguration(BaseHandler):
        RESPONDS_TO = [EventType.Name(EventType.PATIENT_CHART_SUMMARY__SECTION_CONFIGURATION)]
        def compute(self) -> list[Effect]:
            return [
                PatientChartSummaryConfiguration(
                    sections=[
                        PatientChartSummaryConfiguration.CustomSection(name="my_section"),
                        PatientChartSummaryConfiguration.Section.MEDICATIONS,
                        PatientChartSummaryConfiguration.Section.CONDITIONS,
                    ]
                ).apply()
            ]
    ```
##  Full Example 
The following example shows a complete plugin with a custom section that displays a list of items fetched from command metadata.
###  `handlers/my_section.py`
    ```python
    import json
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.patient_chart_summary_custom_section import PatientChartSummaryCustomSection
    from canvas_sdk.effects.patient_chart_summary_configuration import PatientChartSummaryConfiguration
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.handlers.patient_chart_summary_custom_section_handler import PatientChartSummaryCustomSectionHandler
    from canvas_sdk.templates import render_to_string
    from canvas_sdk.v1.data.command import CommandMetadata
    class MySectionHandler(PatientChartSummaryCustomSectionHandler):
        """Handles the PATIENT_CHART_SUMMARY__GET_CUSTOM_SECTION event for 'my_section'.
        Fetches all 'my_section' command metadata entries for the current patient
        and renders them as a list in the section template.
        """
        SECTION_KEY = "my_section"
        def handle(self) -> list[Effect]:
            entries = (
                CommandMetadata.objects.filter(
                    key="my_section",
                    command__patient__id=self.target,
                )
                .order_by("created")
            )
            items = []
            for entry in entries:
                try:
                    items.append(json.loads(entry.value))
                except (json.JSONDecodeError, TypeError):
                    items.append({"title": entry.value})
            return [
                PatientChartSummaryCustomSection(
                    content=render_to_string("templates/my_section.html", {"items": items}),
                    icon="📋",
                ).apply()
            ]
    class MySummaryConfiguration(BaseHandler):
        RESPONDS_TO = [EventType.Name(EventType.PATIENT_CHART_SUMMARY__SECTION_CONFIGURATION)]
        def compute(self) -> list[Effect]:
            return [
                PatientChartSummaryConfiguration(
                    sections=[
                        PatientChartSummaryConfiguration.CustomSection(name="my_section"),
                        PatientChartSummaryConfiguration.Section.MEDICATIONS,
                        PatientChartSummaryConfiguration.Section.CONDITIONS,
                    ]
                ).apply()
            ]
    ```
###  `templates/my_section.html`
    ```html
    <ul>
      {% for item in items %}
        <li>{{ item.title }}</li>
      {% empty %}
        <li>No items on record.</li>
      {% endfor %}
    </ul>
    ```
###  `CANVAS_MANIFEST.json`
    ```json
    {
      "components": {
        "handlers": [
          {
            "class": "my_plugin.handlers.my_section:MySectionHandler",
            "description": "Serves content for the My Section custom chart section"
          },
          {
            "class": "my_plugin.handlers.my_section:MySummaryConfiguration",
            "description": "Configures the chart summary layout to include My Section"
          }
        ]
      }
    }
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/patient-chart-summary-custom-section-handler/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/patient-metadata-create-form-effect/
##  Overview 
This allows developers to dynamically display additional fields in the patient profile. For more guidance please reference ["How to add additional profile fields" guide](https://docs.canvasmedical.com/guides/profile-additional-fields/).
    ```python
    from canvas_sdk.effects.patient_metadata import PatientMetadataCreateFormEffect, InputType, FormField
    PatientMetadataCreateFormEffect(form_fields=[
        FormField(
            key='status',
            label='Status',
            type=InputType.SELECT,
            required=False,
            editable=True,
            options=["open", "close"]
        ),
    ])
    ```
##  Structure 
###  **FormField**
A FormField consists of the following properties:
####  Attributes 
Attribute | Type | Description  
---|---|---  
`key` | `str` | unique identifier of the field - patient metadata key  
`label` | `str` | the label that will be displayed on the field  
`type` | `InputType` | the type of the input - TEXT, SELECT, DATE.  
`required` | `bool` | if the input is required.  
`editable` | `bool` | if the input can be editabled.  
`options` | `list[str]` | possible options for when the input type is set to "SELECT"  
###  **PatientMetadataCreateFormEffect**
A PatientMetadataCreateFormEffect consists of the following properties:
####  Attributes 
Attribute | Type | Description  
---|---|---  
`form_fields` | `list[FormField]` | list of fields.  
----- END PAGE https://docs.canvasmedical.com/sdk/patient-metadata-create-form-effect/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/patient-portal/
The Canvas SDK allows you to configure and extend the patient portal. This page is the technical reference for the portal's effects and events. For a decision-oriented overview of how much of the patient experience to build, see [Choosing Your Patient Experience](/guides/choosing-your-patient-experience/).
##  Configure Portal Menu Items 
Decide which items appear in the patient portal's menu with the `PatientPortalMenuConfiguration` effect, returned from a handler on the `PATIENT_PORTAL__MENU_CONFIGURATION` event. Unlike the account-wide page toggles managed by Canvas Support, this lets you show or hide menu items with your own logic (per patient, program, and so on).
The list you return **replaces** the built-in menu — include every built-in item you want shown, in the order you want them to appear (**the list order is the navigation order**), and omit any you want hidden.
An item only appears if its page is **also enabled in your portal settings** (the `PATIENT_APP_*` settings managed by Canvas Support — see [Managing the Patient Portal](https://canvas-medical.help.usepylon.com/articles/7348270931-managing-the-patient-portal)). This effect selects and orders among the _enabled_ pages: it can hide or reorder them, but it can't surface a page that's turned off. Patient-portal [applications](/sdk/handlers-applications/) aren't part of this list — they're appended to the navigation after the built-in items through their own registration.
Attribute | Type | Description  
---|---|---  
`items` | list[`MenuItems`] | The built-in menu items to show, in navigation order (at least one). Replaces the default set — omit an item to hide it.  
**`MenuItems`** values:
Member | Value | Menu item  
---|---|---  
`APPOINTMENTS` | `"appointments"` | Appointments  
`MESSAGING` | `"messaging"` | Messaging  
`MY_HEALTH` | `"my_health"` | My Health  
`PAYMENTS` | `"payments"` | Payments  
`LABS` | `"labs"` | Lab results  
`CONTACT` | `"contact"` | Contact  
`RECORDS` | `"records"` | Health Records  
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.patient_portal_menu_configuration import PatientPortalMenuConfiguration
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class MyHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.PATIENT_PORTAL__MENU_CONFIGURATION)
        def compute(self) -> list[Effect]:
            return [
                PatientPortalMenuConfiguration(
                    items=[
                        PatientPortalMenuConfiguration.MenuItems.APPOINTMENTS,
                        PatientPortalMenuConfiguration.MenuItems.MESSAGING,
                    ]
                ).apply()
            ]
    ```
##  Portal Landing Page Widgets 
The `PortalWidget` class adds widgets to the patient portal landing page. Return `PortalWidget` effects from a handler on the `PATIENT_PORTAL__WIDGET_CONFIGURATION` event. You can fully customize a widget or use a ready-made one provided by Canvas (Appointments, Messaging).
The `PortalWidget` class has the following attributes:
Attribute | Type | Description  
---|---|---  
`url` | `str` | A URL to load within the widget.  
`content` | `str` | Content to display directly within the widget.  
`component` | `Component` | A ready-made Canvas widget: `APPOINTMENTS` (upcoming appointments) or `MESSAGING` (quick messaging).  
`size` | `Size` | Widget width on the grid: `EXPANDED` (12 columns), `MEDIUM` (8 columns), or `COMPACT` (4 columns). All are 300px tall. Defaults to `EXPANDED`.  
`priority` | `int` | Orders widgets within the portal; a lower number is higher priority. Defaults to `100`.  
**Validation:** exactly one of `url`, `content`, or `component` must be set — providing more than one, or none, raises an error.
###  Example Usage 
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.widgets import PortalWidget
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class MyHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.PATIENT_PORTAL__WIDGET_CONFIGURATION)
        def compute(self) -> list[Effect]:
            return [
                PortalWidget(
                    url="https://example.com/info",
                    size=PortalWidget.Size.COMPACT,
                    priority=25,
                ).apply()
            ]
    ```
For a full walkthrough with examples, see the [Custom Landing Page guide](/guides/custom-landing-page/). Example plugins: [patient_portal_plugin](https://github.com/canvas-medical/canvas-plugins/tree/main/example-plugins/patient_portal_plugin), and the MSF [urgent-care-self-scheduler](https://github.com/medical-software-foundation/canvas/tree/main/extensions/urgent-care-self-scheduler) (a "Need to be seen today?" widget).
##  Customize Appointment Cards 
Customize your patient appointment cards so patients can easily join their telehealth appointments, cancel or reschedule appointments. Each appointment can be customized individually, allowing each one to have its own unique settings.
Each action is decided per appointment: Canvas fires a `PATIENT_PORTAL__APPOINTMENT_CAN_*` event for the appointment, and your handler returns the matching `PATIENT_PORTAL__APPOINTMENT_IS_*` (or `PATIENT_PORTAL__APPOINTMENT_SHOW_MEETING_LINK`) effect with a `{"result": <bool>}` payload — `False` hides the action; for the telehealth **Join** link, `True` shows it. Each action's exact event, effect, and payload are shown below.
MSF example: [portal_disable_cancel_appts](https://github.com/medical-software-foundation/canvas/tree/main/extensions/portal_disable_cancel_appts) hides the Cancel action.
###  Hide 'Cancel' button 
Respond to `PATIENT_PORTAL__APPOINTMENT_CAN_BE_CANCELED` and return a `PATIENT_PORTAL__APPOINTMENT_IS_CANCELABLE` effect with `{"result": False}` to hide the Cancel button.
    ```python
    import json
    from canvas_sdk.effects import Effect, EffectType
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class MyHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.PATIENT_PORTAL__APPOINTMENT_CAN_BE_CANCELED)
        def compute(self) -> list[Effect]:
            return [
              Effect(
                type=EffectType.PATIENT_PORTAL__APPOINTMENT_IS_CANCELABLE,
                payload=json.dumps({"result": False}))
            ]
    ```
###  Hide 'Reschedule' button 
Respond to `PATIENT_PORTAL__APPOINTMENT_CAN_BE_RESCHEDULED` and return a `PATIENT_PORTAL__APPOINTMENT_IS_RESCHEDULABLE` effect with `{"result": False}` to hide the Reschedule button.
    ```python
    import json
    from canvas_sdk.effects import Effect, EffectType
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class MyHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.PATIENT_PORTAL__APPOINTMENT_CAN_BE_RESCHEDULED)
        def compute(self) -> list[Effect]:
            return [
              Effect(
                type=EffectType.PATIENT_PORTAL__APPOINTMENT_IS_RESCHEDULABLE,
                payload=json.dumps({"result": False}))
            ]
    ```
###  Hide 'Join' button 
This button shows on telehealth appointments. Respond to `PATIENT_PORTAL__APPOINTMENT_CAN_SHOW_MEETING_LINK` and return a `PATIENT_PORTAL__APPOINTMENT_SHOW_MEETING_LINK` effect with `{"result": True}` to show the join link.
    ```python
    import json
    from canvas_sdk.effects import Effect, EffectType
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class MyHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.PATIENT_PORTAL__APPOINTMENT_CAN_SHOW_MEETING_LINK)
        def compute(self) -> list[Effect]:
            return [
              Effect(
                type=EffectType.PATIENT_PORTAL__APPOINTMENT_SHOW_MEETING_LINK,
                payload=json.dumps({"result": True}))
            ]
    ```
##  Configuring the Patient Portal 
Return a `PatientPortalApplicationConfiguration` effect from a handler on the `PATIENT_PORTAL__GET_APPLICATION_CONFIGURATION` event to set application-level portal options. Today this controls whether the self-scheduling entry points appear; pair it with Shape Self-Scheduling to control _what_ patients can book.
Attribute | Type | Description  
---|---|---  
can_schedule_appointments | bool | If the patient is allowed to book or reschedule appointments  
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.patient_portal.application_configuration import PatientPortalApplicationConfiguration
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    class MyHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.PATIENT_PORTAL__GET_APPLICATION_CONFIGURATION)
        def compute(self) -> list[Effect]:
            return [
              PatientPortalApplicationConfiguration(
                can_schedule_appointments=True
              ).apply()
            ]
    ```
##  Shape Self-Scheduling 
When patients self-schedule, you can filter or reorder what they're offered _before they see it_ — the open time slots, and the appointment-type, location, and provider options on the scheduling form. Each search step fires a `…PRE_SEARCH` event (before Canvas runs the search) and a `…POST_SEARCH` event (with the results). Respond to the `POST_SEARCH` event and return an effect **of the same name with a`_RESULTS` suffix**, carrying the results you want the patient to see. (A `…PRE_SEARCH` event is available for each step too, if you need to act before the search runs.)
Two example plugins put these together: [patient_portal_search_appointments_slots_plugin](https://github.com/canvas-medical/canvas-plugins/tree/main/example-plugins/patient_portal_search_appointments_slots_plugin) (slots) and [patient_app_schedule](https://github.com/canvas-medical/canvas-plugins/tree/main/example-plugins/patient_app_schedule) (appointment types, locations, and providers).
###  Filter available slots 
Respond to `PATIENT_PORTAL__APPOINTMENTS__SLOTS__POST_SEARCH` and return `PATIENT_PORTAL__APPOINTMENTS__SLOTS__POST_SEARCH_RESULTS`.
**Target:** `self.target` is the [Patient](/sdk/data-patient/#patient) id.
**Context:** `self.context["slots_by_provider"]` — a JSON string of `{provider: {date: [{"start", "end"}, ...]}}`, keyed by [Staff](/sdk/data-staff/#staff) id, where each `start`/`end` is an ISO-8601 datetime.
Example — enforce a 48-hour minimum lead time so patients can't self-book last minute:
    ```python
    import json
    import arrow
    from canvas_sdk.effects import Effect, EffectType
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    # Patients must book at least this far ahead — no last-minute self-scheduling
    MIN_LEAD_TIME_HOURS = 48
    class SlotFilter(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.PATIENT_PORTAL__APPOINTMENTS__SLOTS__POST_SEARCH)
        def compute(self) -> list[Effect]:
            # slots_by_provider maps each provider to {date: [{"start": ..., "end": ...}, ...]}
            slots_by_provider = json.loads(self.context.get("slots_by_provider") or "{}")
            cutoff = arrow.now().shift(hours=MIN_LEAD_TIME_HOURS)
            filtered = {}
            for provider, dates in slots_by_provider.items():
                # drop any slot starting before the cutoff
                kept = {
                    date: [slot for slot in slots if arrow.get(slot["start"]) >= cutoff]
                    for date, slots in dates.items()
                }
                # then drop empty dates, and providers left with no slots
                kept = {date: slots for date, slots in kept.items() if slots}
                if kept:
                    filtered[provider] = kept
            return [
                Effect(
                    type=EffectType.PATIENT_PORTAL__APPOINTMENTS__SLOTS__POST_SEARCH_RESULTS,
                    payload=json.dumps({"slots_by_provider": filtered}),
                )
            ]
    ```
###  Filter appointment types 
Respond to `PATIENT_PORTAL__APPOINTMENTS__FORM_APPOINTMENT_TYPES__POST_SEARCH` and return `PATIENT_PORTAL__APPOINTMENTS__FORM_APPOINTMENT_TYPES__POST_SEARCH_RESULTS`.
**Target:** `self.target` is the [Patient](/sdk/data-patient/#patient) id.
**Context:** `self.context["appointment_types"]` — a list of `{"id", "title"}` entries, where `id` is a [NoteType](/sdk/data-note/#notetype) id (its `unique_identifier`) and `title` is the note type's name.
Example — only let patients self-book a specific set of visit types (e.g. keep follow-ups and telehealth, hide new-patient intakes):
    ```python
    import json
    from canvas_sdk.effects import Effect, EffectType
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    # Visit types patients are allowed to self-book
    PATIENT_BOOKABLE_TYPES = {"Follow-up", "Telehealth visit"}
    class AppointmentTypeFilter(BaseHandler):
        RESPONDS_TO = EventType.Name(
            EventType.PATIENT_PORTAL__APPOINTMENTS__FORM_APPOINTMENT_TYPES__POST_SEARCH
        )
        def compute(self) -> list[Effect]:
            # each entry is {"id": ..., "title": ...}
            appointment_types = self.context.get("appointment_types", [])
            filtered = [t for t in appointment_types if t["title"] in PATIENT_BOOKABLE_TYPES]
            return [
                Effect(
                    type=EffectType.PATIENT_PORTAL__APPOINTMENTS__FORM_APPOINTMENT_TYPES__POST_SEARCH_RESULTS,
                    payload=json.dumps({"appointment_types": filtered}),
                )
            ]
    ```
###  Filter locations 
Respond to `PATIENT_PORTAL__APPOINTMENTS__FORM_LOCATIONS__POST_SEARCH` and return `PATIENT_PORTAL__APPOINTMENTS__FORM_LOCATIONS__POST_SEARCH_RESULTS`.
**Target:** `self.target` is the [Patient](/sdk/data-patient/#patient) id.
**Context:** `self.context["locations"]` — a list of `{"id", "title"}` entries, where `id` is a [PracticeLocation](/sdk/data-practicelocation/#practicelocation) id and `title` is its full name.
Example — only show locations where the patient has been seen before:
    ```python
    import json
    from canvas_sdk.effects import Effect, EffectType
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.v1.data import Appointment
    class LocationFilter(BaseHandler):
        RESPONDS_TO = EventType.Name(
            EventType.PATIENT_PORTAL__APPOINTMENTS__FORM_LOCATIONS__POST_SEARCH
        )
        def compute(self) -> list[Effect]:
            locations = self.context.get("locations", [])
            seen_location_ids = {
                str(loc_id)
                for loc_id in Appointment.objects.filter(patient__id=self.target)
                .exclude(location__isnull=True)
                .values_list("location__id", flat=True)
            }
            filtered = [loc for loc in locations if loc["id"] in seen_location_ids]
            return [
                Effect(
                    type=EffectType.PATIENT_PORTAL__APPOINTMENTS__FORM_LOCATIONS__POST_SEARCH_RESULTS,
                    payload=json.dumps({"locations": filtered}),
                )
            ]
    ```
###  Filter providers 
Respond to `PATIENT_PORTAL__APPOINTMENTS__FORM_PROVIDERS__POST_SEARCH` and return `PATIENT_PORTAL__APPOINTMENTS__FORM_PROVIDERS__POST_SEARCH_RESULTS`.
**Target:** `self.target` is the [Patient](/sdk/data-patient/#patient) id.
**Context:** `self.context["providers"]` — a list of `{"id", "title"}` entries, where `id` is a [Staff](/sdk/data-staff/#staff) id and `title` is the provider's name and roles.
Example — only offer the patient's own care-team providers:
    ```python
    import json
    from canvas_sdk.effects import Effect, EffectType
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.v1.data import CareTeamMembership
    from canvas_sdk.v1.data.care_team import CareTeamMembershipStatus
    class ProviderFilter(BaseHandler):
        RESPONDS_TO = EventType.Name(
            EventType.PATIENT_PORTAL__APPOINTMENTS__FORM_PROVIDERS__POST_SEARCH
        )
        def compute(self) -> list[Effect]:
            providers = self.context.get("providers", [])
            care_team_ids = {
                str(staff_id)
                for staff_id in CareTeamMembership.objects.filter(
                    patient__id=self.target, status=CareTeamMembershipStatus.ACTIVE
                ).values_list("staff__id", flat=True)
            }
            filtered = [p for p in providers if p["id"] in care_team_ids]
            return [
                Effect(
                    type=EffectType.PATIENT_PORTAL__APPOINTMENTS__FORM_PROVIDERS__POST_SEARCH_RESULTS,
                    payload=json.dumps({"providers": filtered}),
                )
            ]
    ```
##  Forms 
Forms let you dynamically display questionnaires to patients in the portal based on your own criteria, and commit each response to the patient's chart as a Questionnaire Command when they submit. Because your handler runs on every portal page load, return only the forms that should currently appear.
> **Warning:** Set `create_command=True` on almost every form. It's what puts the patient's response in the chart — as a committed Questionnaire Command in a note. Without it, the response is saved only as an Interview and never surfaces in the chart, so the care team won't see it. Leave it off only when you have a specific reason. 
Respond to the `PATIENT_PORTAL__GET_FORMS` event and return a `FormResult` effect for each questionnaire to show.
Attribute | Type | Description  
---|---|---  
`questionnaire_id` | `str` | `UUID` | The unique ID of the [Questionnaire](/sdk/data-questionnaire/#questionnaire) to show.  
`create_command` | `bool` | **Strongly recommended`True`.** Commits the response as a Questionnaire Command in a note so it appears in the chart. When `False` (the default), the response is saved only as an Interview and never appears in a note.  
`note_id` | `str` | `UUID` | `None` | The [Note](/sdk/data-note/#note) to place the command on. Only used when `create_command` is `True`; if omitted, Canvas creates and locks a new note for it. Has no effect on its own.  
**Example** — assign intake questionnaires when a patient has a confirmed upcoming visit, skipping any they've already completed:
    ```python
    import arrow
    from canvas_sdk.effects.patient_portal.form_result import FormResult
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.v1.data import Interview, Patient, Questionnaire
    from canvas_sdk.v1.data.appointment import AppointmentProgressStatus
    INTAKE_QUESTIONNAIRES = ["Insurance Details", "Preferred Pharmacy Details", "Social History"]
    APPOINTMENT_NOTE_TYPES = ["Telehealth", "Office visit"]
    class Handler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.PATIENT_PORTAL__GET_FORMS)
        def _upcoming_appointment_note_id(self, appointments):
            return appointments.filter(
                status=AppointmentProgressStatus.CONFIRMED,
                start_time__gt=arrow.now().date(),
                note_type__name__in=APPOINTMENT_NOTE_TYPES,
            ).values_list("note__id", flat=True).first()
        def compute(self):
            patient = Patient.objects.get(id=self.target)
            # Only assign intake forms when there's a confirmed upcoming visit
            note_id = self._upcoming_appointment_note_id(patient.appointments)
            if not note_id:
                return []
            # Which intake questionnaires are already completed *on this visit's note*.
            # committed() automatically drops entered-in-error and uncommitted interviews.
            # The same intake form is expected each visit, so scope the check to this note
            # rather than the whole patient.
            completed = set(
                Interview.objects.for_patient(patient.id)
                .committed()
                .filter(note_id=note_id, questionnaires__name__in=INTAKE_QUESTIONNAIRES)
                .values_list("questionnaires__name", flat=True)
            )
            missing = [name for name in INTAKE_QUESTIONNAIRES if name not in completed]
            missing_ids = Questionnaire.objects.filter(name__in=missing).values_list("id", flat=True)
            return [
                FormResult(questionnaire_id=qid, create_command=True, note_id=note_id).apply()
                for qid in missing_ids
            ]
    ```
**Best practices:**
  - **Match questionnaires by name, not ID — it always resolves to the latest version.** When a questionnaire is updated, the previous version is archived with a `(v#)` suffix on its name (e.g. `Social History (v1)`) while the current version keeps the clean name. Filtering by the exact name therefore returns only the latest version, and (as a bonus) stays portable across instances, where IDs differ.
  - **Prevent duplicates — but scope the check.** This logic runs on every page load, so check existing committed `Interview` responses before assigning a form again. Because the same intake form is typically expected at every visit, scope that check to the current appointment's `note_id` rather than the whole patient (use `.committed()` so entered-in-error responses are ignored automatically).
  - **Don't let forms linger.** Return a questionnaire only while it should be shown; once it's completed or no longer relevant, stop returning it.
  - **Always land responses in the chart.** Set `create_command=True` so each submission is committed as a Questionnaire Command; pass a `note_id` to target a specific note, or omit it to let Canvas create one. Without `create_command`, the response is only an Interview and the care team won't see it.
Real-world example: the MSF [patient-portal-forms](https://github.com/medical-software-foundation/canvas/tree/main/extensions/patient-portal-forms) extension assigns questionnaires (PHQ-9, GAD-7, ROS…) that patients complete in the portal, posting responses back to the chart.
##  User Login 
These effects manage a patient's portal **user account** — the login they use to access the portal. Invites and password resets are sent to the email or phone linked on the user, so make sure those contact points are actually **verified** before you invite a patient or rely on an updated value. Trigger a verification with the [Send Contact Verification](/sdk/effect-send-contact-verification/) effect.
###  Update User 
The `UpdateUserEffect` updates a portal user's phone number or email. In the future this may support additional attributes, but for now only these two are supported. It isn't tied to a specific portal event — emit it from whatever handler fits your workflow (a common one is keeping the portal user in sync in response to `PATIENT_CONTACT_POINT_UPDATED`, as below).
Attribute | Type | Required | Description  
---|---|---|---  
`user_dbid` | `int` | `true` | The `dbid` of the [CanvasUser](/sdk/data-canvasuser/#user) to update.  
`phone_number` | `str` | `false`* | The phone number to store.  
`email` | `str` | `false`* | The email to store.  
**Validation:** at least one of `phone_number` or `email` must be provided, and `user_dbid` must resolve to an existing user (otherwise the effect returns a `User does not exist` error).
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.update_user import UpdateUserEffect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.v1.data import PatientContactPoint
    from canvas_sdk.v1.data.common import ContactPointSystem
    class SyncPortalUser(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.PATIENT_CONTACT_POINT_UPDATED)
        def compute(self) -> list[Effect]:
            contact_point = PatientContactPoint.objects.get(id=self.target)
            # Only sync verified contact points. last_verified is set once the patient
            # completes the verification challenge (see Send Contact Verification); until
            # then the value shouldn't be trusted as the account's login channel.
            if not contact_point.last_verified:
                return []
            user = contact_point.patient.user
            # Only update when the value actually changed. Emitting an effect that sets the
            # user to what it already is is a no-op — and can retrigger
            # PATIENT_CONTACT_POINT_UPDATED, causing an update loop.
            if contact_point.system == ContactPointSystem.EMAIL and user.email != contact_point.value:
                return [UpdateUserEffect(user_dbid=user.dbid, email=contact_point.value).apply()]
            if contact_point.system == ContactPointSystem.PHONE and user.phone_number != contact_point.value:
                return [UpdateUserEffect(user_dbid=user.dbid, phone_number=contact_point.value).apply()]
            return []
    ```
###  Send Invite 
The `SendInviteEffect` triggers a portal invitation that lets the patient register or activate their portal account. Because the invite is delivered to the patient's email or phone, only send it once that contact point is **verified** (`PatientContactPoint.last_verified` is set — see [Send Contact Verification](/sdk/effect-send-contact-verification/)) and the patient hasn't already registered (`CanvasUser.is_portal_registered`). Verification completes with a save that fires `PATIENT_CONTACT_POINT_UPDATED`, which makes it a natural trigger for the invite. Re-inviting someone who has already activated their account just sends a redundant email/SMS.
Attribute | Type | Required | Description  
---|---|---|---  
`user_dbid` | `int` | `true` | The `dbid` of the [CanvasUser](/sdk/data-canvasuser/#user) to invite.  
**Validation:** `user_dbid` must resolve to an existing user (otherwise the effect returns a `User does not exist` error).
    ```python
    from canvas_sdk.effects import Effect
    from canvas_sdk.effects.send_invite import SendInviteEffect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.v1.data import PatientContactPoint
    class InviteVerifiedPatient(BaseHandler):
        # Verification completes with a save that fires this event
        RESPONDS_TO = EventType.Name(EventType.PATIENT_CONTACT_POINT_UPDATED)
        def compute(self) -> list[Effect]:
            contact_point = PatientContactPoint.objects.get(id=self.target)
            # Only invite once the contact point is actually verified — that's the
            # channel the invitation will be delivered to.
            if not contact_point.last_verified:
                return []
            user = contact_point.patient.user
            # Only invite once: skip if there's no portal user yet, or they've already registered
            if user is None or user.is_portal_registered:
                return []
            return [SendInviteEffect(user_dbid=user.dbid).apply()]
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/patient-portal/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/payment-processor-effect/
These effects are returned from a custom [Payment Processor](/sdk/handlers-payment-processors/) handler in response to the `REVENUE__PAYMENT_PROCESSOR__*` [events](/sdk/events/#payment-processor-events). Each effect is the handler's reply to a specific stage of a payment workflow — advertising the processor, rendering a form, charging a card, or managing a patient's saved payment methods.
All of the classes below are importable from `canvas_sdk.effects.payment_processor`.
##  PaymentProcessorMetadata 
Advertises a payment processor to Canvas in response to `REVENUE__PAYMENT_PROCESSOR__LIST`. Canvas uses it to discover which processors are installed and to route later events (charge, add card, etc.) to the right handler.
**You normally never construct this yourself.** The base `PaymentProcessor` handler builds and returns it automatically through its `metadata()` method, which fills in:
  - `identifier` — a stable, unique id derived from your handler class (its module path and class name), exposed as `self.identifier`. Canvas includes this `identifier` in every subsequent payment processor event so your handler knows the event is meant for it.
  - `type` — the processor's `TYPE` class attribute (for example, `CardPaymentProcessor` sets this to `CARD`).
Because the base handler already responds to `REVENUE__PAYMENT_PROCESSOR__LIST` with this effect, you only need to construct it directly in advanced cases where you override that default behavior.
Attribute |  | Type | Description  
---|---|---|---  
identifier | required | String | Unique identifier of the payment processor. Generated automatically per handler.  
type | required | PaymentProcessorType | The kind of processor. Currently only `CARD` is supported.  
###  PaymentProcessorType 
Value | Description  
---|---  
`CARD` | A card-based processor.  
##  PaymentProcessorForm 
Returns the HTML form used to collect and tokenize card details, in response to `REVENUE__PAYMENT_PROCESSOR__SELECTED`. The `content` is rendered as inner HTML inside Canvas and must implement the [Form Workflow](/sdk/handlers-payment-processors/#form-workflow).
Attribute |  | Type | Description  
---|---|---|---  
intent | required | String | The purpose of the form. One of `"pay"` or `"add_card"`.  
content | required | String | The HTML content to render inside Canvas.  
    ```python
    from canvas_sdk.effects.payment_processor import PaymentProcessorForm
    from canvas_sdk.templates import render_to_string
    from canvas_sdk.v1.data import Patient
    def payment_form(self, patient: Patient | None = None) -> PaymentProcessorForm:
        content = render_to_string("templates/payment_form.html")
        return PaymentProcessorForm(intent="pay", content=content)
    ```
##  CardTransaction 
Returns the result of charging a card, in response to `REVENUE__PAYMENT_PROCESSOR__CHARGE`.
Attribute |  | Type | Description  
---|---|---|---  
success | required | Boolean | Whether the charge succeeded.  
transaction_id | required | String | None | The identifier of the transaction, if one was created.  
api_response | required | Dictionary | The raw response returned by the payment provider.  
error_code | optional | String | None | An error code describing why the charge failed, if applicable.  
    ```python
    from decimal import Decimal
    from typing import Any
    from canvas_sdk.effects.payment_processor import CardTransaction
    from canvas_sdk.v1.data import Patient
    def charge(
        self, amount: Decimal, token: str, patient: Patient | None = None, **kwargs: Any
    ) -> CardTransaction:
        return CardTransaction(
            success=True,
            transaction_id="txn_123",
            api_response={"status": "succeeded"},
        )
    ```
##  PaymentMethod 
Represents a patient's saved payment method, returned in response to `REVENUE__PAYMENT_PROCESSOR__PAYMENT_METHODS__LIST`.
The cards managed by your custom processor live with your third-party payment provider — your processor is responsible for persisting them there when a card is added and deleting them when a card is removed. Canvas does not persist them for you; it asks your handler for the current list each time it needs to display saved cards, and the values you return here are rendered directly.
Attribute |  | Type | Description  
---|---|---|---  
payment_method_id | required | String | The identifier of the saved payment method.  
brand | required | String | The card brand (e.g. `"Visa"`).  
card_holder_name | required | String | None | The name of the card holder.  
expiration_year | required | Integer | The card's expiration year.  
expiration_month | required | Integer | The card's expiration month.  
card_last_four_digits | required | String | The last four digits of the card number.  
postal_code | optional | String | None | The billing postal code.  
country | optional | String | None | The billing country.  
    ```python
    from canvas_sdk.effects.payment_processor import PaymentMethod
    from canvas_sdk.v1.data import Patient
    def payment_methods(self, patient: Patient | None = None) -> list[PaymentMethod]:
        return [
            PaymentMethod(
                payment_method_id="pm_1",
                brand="Visa",
                card_holder_name="John Doe",
                expiration_year=2030,
                expiration_month=12,
                card_last_four_digits="4242",
                postal_code="12345",
            )
        ]
    ```
##  AddPaymentMethodResponse 
Returns the result of adding a payment method, in response to `REVENUE__PAYMENT_PROCESSOR__PAYMENT_METHODS__ADD`.
Attribute |  | Type | Description  
---|---|---|---  
success | required | Boolean | Whether the payment method was added.  
    ```python
    from typing import Any
    from canvas_sdk.effects.payment_processor import AddPaymentMethodResponse
    from canvas_sdk.v1.data import Patient
    def add_payment_method(self, token: str, patient: Patient, **kwargs: Any) -> AddPaymentMethodResponse:
        return AddPaymentMethodResponse(success=True)
    ```
##  RemovePaymentMethodResponse 
Returns the result of removing a payment method, in response to `REVENUE__PAYMENT_PROCESSOR__PAYMENT_METHODS__REMOVE`.
Attribute |  | Type | Description  
---|---|---|---  
success | required | Boolean | Whether the payment method was removed.  
    ```python
    from canvas_sdk.effects.payment_processor import RemovePaymentMethodResponse
    from canvas_sdk.v1.data import Patient
    def remove_payment_method(self, token: str, patient: Patient) -> RemovePaymentMethodResponse:
        return RemovePaymentMethodResponse(success=True)
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/payment-processor-effect/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/plugin-logs/
Canvas provides two ways to access **Plugin Logs** :
  - **UI:** `https://<your-instance>.canvasmedical.com/admin/plugin-io` → **Logs**
  - **CLI:** `canvas logs --help`
This guide explains how to open the UI view and how to use the CLI to filter, paginate, and (optionally) follow live logs.
* * *
###  Plugin Logs in the Admin UI 
From the Django Admin:
Navigation path:  
`Home` › `Plugin_IO` › **Plugin Logs**
The UI lets you:
  - Filter by **source** (e.g., `plugin-runner`, `effect-interpreter`)
  - Filter by **level** (`ERROR`, `WARN`, `INFO`, `DEBUG`)
  - Filter by **plugin** — multi-select; the dropdown is pre-populated with plugins seen in the last 7 days, and accepts free-text entries for older or yet-to-log plugins
  - Filter by **handler** — multi-select on the fully-qualified handler class; works the same as plugin (last 7 days + free-text)
  - Filter by **time** (start/end)
  - Inspect **full JSON** of a log entry in a modal
  - **Load more** results without leaving the page
The results table shows columns for `@timestamp`, `level`, `source`, `plugin`, and `message`. Click a row to open the full log entry as JSON.
> **Info:** The UI defaults to showing the most recent logs first (sorted by `@timestamp desc`). 
* * *
###  CLI Overview 
`canvas logs` now supports **historical lookback** , **filters** , **stateless pagination** with **cursors** , and **interactive paging** , all without breaking the original behavior.
  - **Default (no flags)** → live stream (unchanged)
  - **Add a time window** → fetch history (tail), then **follow** by default
  - **Stop after history** → `--no-follow`
  - **Page through large result sets** → `--limit`, `--page-size`, `--interactive`, or **cursor** tokens
Run `canvas logs --help` to see all options.
###  Common Filters & Examples 
#####  Filter by source 
    ```console
    $ canvas logs --source plugin-runner
    ```
#####  Filter by level (repeat flag) 
    ```console
    # Only errors:
    $ canvas logs --level ERROR
    # Errors and warnings:
    $ canvas logs --level ERROR --level WARN
    ```
#####  Filter by plugin / handler (repeatable) 
    ```console
    # One plugin:
    $ canvas logs --plugin my_plugin
    # Multiple plugins:
    $ canvas logs --plugin my_plugin --plugin other_plugin
    # A specific handler (fully qualified class name):
    $ canvas logs --handler my_plugin.handlers.foo.MyHandler
    # Multiple handlers:
    $ canvas logs --handler my_plugin.handlers.foo.MyHandler --handler my_plugin.handlers.bar.OtherHandler
    ```
#####  Time windows: since / start / end 
**Relative lookback (`--since`)**  
Fetch the last 24 hours, then continue following:
    ```console
    $ canvas logs --since 24h
    ```
**Absolute window (`--start/--end`)**  
Fetch a fixed window and stop:
    ```console
    $ canvas logs --start "2025-09-12T10:00:00Z" --end "2025-09-12T12:00:00Z" --no-follow
    ```
> **Info:** `--since` is mutually exclusive with `--start/--end`. 
#####  Combine filters 
    ```console
    # Errors from plugin-runner in the last 2 hours:
    $ canvas logs --since 2h --level ERROR --source plugin-runner --no-follow
    ```
* * *
###  Interactive Mode 
Use `--interactive` in **historical** mode to page through results one page at a time:
    ```console
    $ canvas logs --no-follow --since 24h --interactive
    # Shows one page, prompts:
    # Load more? [Y/n]
    ```
  - The prompt repeats after each page.
* * *
###  Stateless Paging with Cursors 
When more results are available, the CLI prints a **resume command** with a **cursor token** (encodes the `search_after` and original filters). Re-run it to continue exactly where you left off:
    ```console
    More available. To load the next page, run:
      canvas logs \
      --no-follow \
      --cursor <TOKEN>
    ```
> **Warning:** `--cursor` is **mutually exclusive** with filters (`--since`, `--start/--end`, `--level`, `--source`, `--plugin`, `--handler`) to enforce consistency. Use the token alone to resume 
.
* * *
###  Limits & Page Size 
  - **`--page-size`** : how many logs to fetch per request (batching).  
Default is optimized for typical usage.
  - **`--limit`** : maximum number of logs to print **across pages**.
Examples:
    ```console
    # One fixed page of size 200 (default page-size):
    $ canvas logs --no-follow --since 24h
    # Fetch up to 2000 logs across pages (non-interactive):
    $ canvas logs --no-follow --since 72h --limit 2000
    # Smaller batches for slow connections:
    $ canvas logs --no-follow --since 24h --limit 1000 --page-size 100
    ```
> **Info:** In non-interactive, no-limit mode, the CLI prints **one page**. Add `--limit`, `--interactive`, or `--all` to keep paging. 
* * *
###  No-Follow (Historical Only) 
Add `--no-follow` to fetch **only** historical logs and exit:
    ```console
    $ canvas logs --no-follow --since 24h
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/plugin-logs/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/protocols/
The handlers module lets you define workflows and workflow automations. Handlers respond to [Events](/sdk/events/) and return zero, one, or many [Effects](/sdk/effects/).
`ClinicalQualityMeasure` is a specialized handler base class for clinical protocols — see ClinicalQualityMeasure below.
##  Contents 
  - BaseHandler
  - ClinicalQualityMeasure
##  BaseHandler 
`BaseHandler` is the abstract base class all handler implementations inherit from. It provides the lifecycle and surface area plugin authors implement for event-driven handlers.
###  Purpose & lifecycle 
  - The framework will call `compute()` on the handler instance when an event should be handled. `compute()` must return a list of `Effect` objects that the runtime will apply.
  - Handlers must override the `compute()` method.
###  Constructor and attributes 
  - Your handler should inherit from `BaseHandler` and define the following:
    - `RESPONDS_TO` — The `Event(s)` that trigger the handler.
    - `compute` — The method that handles the Event and returns a list of Effects.
  - Instance attributes available to handler authors:
    - `self.event` — The `Event` instance.
    - `self.secrets` — Secrets provided to the handler (defaults to {}).
###  Example 
    ```python
    from canvas_sdk.handlers.base import BaseHandler
    from canvas_sdk.events import EventType
    from canvas_sdk.effects.task import AddTask
    class SimpleFollowUpHandler(BaseHandler):
        RESPONDS_TO = EventType.Name(EventType.IMAGING_REPORT_CREATED)
        def compute(self):
            # Use self.event, self.secrets
            patient_id = self.event.context["patient"]["id"]
            imaging_report_id = self.event.target.id
            # Create a follow-up task effect
            return [AddTask(patient_id=patient_id, title="Follow-up", linked_object_type=AddTask.LinkableObjectType.IMAGING, linked_object_id=imaging_report_id).apply()]
    ```
##  ClinicalQualityMeasure 
`ClinicalQualityMeasure` is the base class for clinical quality measure (CQM) protocols. CQMs are patient-centric protocols used to evaluate, detect, or surface clinical conditions, gaps in care, and population-level metrics. Plugin authors create concrete subclasses that implement the clinical logic and return Effects in response to incoming Events.
When using `ClinicalQualityMeasure`, you have the option to utilize the [Campaigns](https://help.canvasmedical.com/articles/3097826946-campaigns-populations-patients) module in Canvas. However, the `ClinicalQualityMeasure` must return a single [`ProtocolCard`](/sdk/effect-protocol-cards/) effect in order to for patients to be included in the population for that CQM.
###  Meta properties 
Subclasses should populate the `Meta` inner class. Common meta fields include:
  - `title` (str): Human-readable title for the protocol.
  - `identifiers` (list[str]): One or more external identifiers for the measure (for example, CMS/QDM ids). These will show in the subtitle of the protocol card.
  - `description` (str): A short description of what the measure evaluates.
  - `information` (str): Longer contextual information or rationale.
  - `references` (list[str]): Links or identifiers for authoritative references. These are visible in the info button on the protocol card.
  - `source_attributes` (dict[str, str]): Map of the 13 or 31 source attributes that certified health IT developers must reference when implementing DSI or PDSI. These are visible in the info button on the protocol card.
  - `types` (list[str]): Tags or classification strings for the measure, like "CQM" or "HCC". These are visible in the subtitle of the protocol card.
  - `authors` (list[str]): Authors or maintainers of the protocol.
  - `show_in_chart` (bool): Determines whether the protocol card will show on the patient's chart.
  - `show_in_population` (bool): Determines whether the protocol will be included in the Campaigns module of Canvas.
  - `can_be_snoozed` (bool): Determines whether a user can snooze the protocol card to be addressed at a later date.
  - `is_abstract`, `is_predictive` (bool): Behavioral flags for the framework.
###  Key methods 
  - `timeframe` (property) -> `Timeframe`
    - Provides the default timeframe used by the protocol when searching for relevant events or records. The default implementation returns a timeframe with a start one year before now and an end at the current time. Subclasses can override this property to adjust the window of interest.
  - `relative_float(value: str) -> float`
    - Parses comparison-style numeric strings that may include relational prefixes like `<`, `<=`, `>` or `>=`. Returns a float adjusted slightly (±1e-6) for strict `<` or `>` operators so comparisons can be expressed without ambiguity. If parsing fails, returns `0`.
  - `patient_id_from_target()` -> str
    - Extracts and caches the patient id from a protocol event target for supported event types. The method supports a variety of patient-centric event targets (Conditions, LabOrders, LabReports, Medications, Patient create/update events, and ProtocolOverride events). The first call will fetch and cache the patient id to avoid repeated DB lookups. If an unsupported event type is provided a `ValueError` is raised.
###  Example — react to a lab report 
This example shows a protocol that reacts to `LAB_REPORT_CREATED` events, uses `patient_id_from_target` to determine which patient the report belongs to, and emits an Effect when a particular lab value is out of range. Note the example avoids heavy synchronous DB work and emits an Effect for the platform to handle asynchronously.
    ```python
    from datetime import datetime
    from canvas_sdk.commands import TaskCommand
    from canvas_sdk.effects.protocol_card import ProtocolCard
    from canvas_sdk.events import EventType
    from canvas_sdk.protocols.clinical_quality_measure import ClinicalQualityMeasure
    class AbnormalPotassiumMeasure(ClinicalQualityMeasure):
        """Detects clinically significant potassium abnormalities and surfaces follow-up tasks."""
        class Meta:
            title = "Abnormal Potassium Alert"
            identifiers = ["CQM-K-001"]
            description = "Creates a task recommendation when a potassium lab report shows hypokalemia or hyperkalemia."
            information = (
                "Detects clinically significant potassium abnormalities and surfaces follow-up tasks."
            )
            references = ["Potassium Guideline https://example.org/guideline/potassium"]
            source_attributes = {"Canvas Medical": "Canvas Medical https://www.canvasmedical.com"}
            types = ["CQM"]
            authors = ["Clinical Team"]
            show_in_chart = True
            show_in_population = True
            can_be_snoozed = False
            is_abstract = False
            is_predictive = False
        RESPONDS_TO = EventType.Name(EventType.LAB_REPORT_CREATED)
        def compute(self):
            # Resolve patient id (cached on first call)
            patient_id = self.patient_id_from_target()
            # Read the lab report values from the event target (avoid extra DB queries here)
            report = self.event.target
            potassium_value = report.get_value('potassium')  # simplified accessor
            # Use relative_float to safely parse any comparator-style values
            k = self.relative_float(str(potassium_value))
            if k < 3.5 or k > 5.5:
                # Emit an effect — e.g., create a task. Keep heavy work to platform handlers.
                task = TaskCommand(
                    title="Follow-up on abnormal potassium",
                    due_date=datetime.now().date(),
                )
                return [
                    ProtocolCard(
                        patient_id=patient_id,
                        title=f"Abnormal potassium: {k}",
                        due=datetime.now(),
                        key="abnormal_potassium",
                        narrative="Talk to patient about potassium",
                        recommendations=[task.recommend(title="Follow-up on abnormal potassium")],
                    ).apply()
                ]
            return []
    ```
![ProtocolCard]("/assets/images/sdk/handlers/protocol_card_example.png")
When a CQM protocol that returns a single ProtocolCard effect is uploaded to Canvas, you can select the protocol as an option in the Campaigns module and view the population of patients, create campaigns, etc. More details on Populations and Campaigns can be found [here](https://help.canvasmedical.com/articles/3097826946-campaigns-populations-patients).
###  Caveats & notes 
  - Timeframe: by default the protocol looks at the 1-year window prior to now. Override `timeframe` if your measure requires a broader or narrower lookback.
  - patient id resolution: `patient_id_from_target()` supports only the event types enumerated by the implementation; verify the event you plan to subscribe to maps to a supported model. When used heavily, this method avoids extra DB queries by caching the patient id on the instance.
  - Event-driven handlers should avoid expensive synchronous DB operations inside their event handler. When possible, emit Effects that are handled asynchronously by the platform.
----- END PAGE https://docs.canvasmedical.com/sdk/protocols/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/questionnaires/
Questionnaires are a structured set of questions intended to guide the collection of answers from end-users.
##  How to build and install a Questionnaire 
To include a questionnaire in your plugin, add a reference to its YAML template inside the `questionnaires` section of the `CANVAS_MANIFEST.json` file.
###  Example Manifest Configuration 
    ```json
    {
        "sdk_version": "0.1.4",
        "plugin_version": "0.0.1",
        "name": "example_questionnaire",
        "description": "Edit the description in CANVAS_MANIFEST.json",
        "components": {
            "handlers": [],
            "commands": [],
            "content": [],
            "effects": [],
            "views": [],
            "questionnaires": [
                {
                    "template": "templates/example_questionnaire.yml"
                }
            ]
        },
        "variables": [],
        "tags": {},
        "references": [],
        "license": "",
        "diagram": false,
        "readme": "./README.md"
    }
    ```
###  Key Properties 
  - **`template`** : The relative path to the YAML file defining the questionnaire.
The questionnaire YAML file should adhere to the JSON schema found [here](https://raw.githubusercontent.com/canvas-medical/canvas-plugins/main/schemas/questionnaire.json) that is also listed below.
##  JSON Schema Reference 
###  Questionnaire Settings 
Property | Description | Required  
---|---|---  
`name` | Name of the Questionnaire. | Yes  
`form_type` | Specifies the use case: `QUES` (Questionnaire), `SA` (Structured Assessment), `EXAM` (Physical Exam), or `ROS` (Review of Systems). | Yes  
`code_system` | The coding system used for the questionnaire, e.g., `SNOMED`, `LOINC`, `INTERNAL`, `ICD-10`, `CPT`, `CANVAS`. | Yes  
`code` | The assigned code for the questionnaire (e.g., `72109-2`). | Yes  
`can_originate_in_charting` | Specifies if the questionnaire can be initiated from charting. Values: `TRUE` or `FALSE`. | Yes  
`prologue` | Text displayed at the beginning of the questionnaire to provide context to the user. | No  
`display_results_in_social_history_section` | Determines if completion information should be displayed in the Social History (SHX) section. Values: `TRUE` or `FALSE`. Default: `FALSE`. | No  
`questions` | List of questions in the questionnaire. See below. | Yes  
###  Question Settings 
Property | Description | Required  
---|---|---  
`code_system` | The coding system for the question. Options: `SNOMED`, `LOINC`, `INTERNAL`, `ICD-10`, `CPT`, `CANVAS`. | Yes  
`code` | The assigned code for the question. Example: `44250-9`. Codes should be unique within the same questionnaire. | Yes  
`content` | The text displayed when the command is printed. | Yes  
`responses_code_system` | The coding system for responses. Options: `SNOMED`, `LOINC`, `INTERNAL`, `ICD-10`, `CPT`. | Yes  
`responses_type` | Response type: `SING` (Single Select), `MULT` (Multi Select), `TXT` (Free Text), or `DATE` (Date). | Yes  
`display_result_in_social_history_section` | Determines if the response should be shown in the Social History (SHX) section. Values: `TRUE` or `FALSE`. Default: `FALSE`. | No  
`enabled_behavior` | Specifies if `all` or `any` of the `enabled_conditions` must be met to enable this question. Only needed when there are multiple conditions. Values: `all`, `any`. | No  
`enabled_conditions` | List of conditions that control when this question is displayed. See below. | No  
`responses` | List of responses for the question. See below. | Yes  
###  Enabled Condition Settings 
Property | Description | Required  
---|---|---  
`question_code` | The code of the question whose answer is evaluated. | Yes  
`operator` | The comparison operator. Supported values: `=`, `!=`, `exists`, `not_exists`. | Yes  
`value_code` | The response option code to match against. Used with `=` or `!=` for single/multi select questions. | No  
`value_string` | The free text value to match against. Used with `=` or `!=` for free text questions. | No  
###  Response Settings 
Property | Description | Required  
---|---|---  
`name` | For `SING`/`MULT`, this is the text that will be displayed for each response. For `TXT`, enter "TXT". For `DATE`, enter "DATE". | Yes  
`code` | The assigned code for the response. Example: `Z759`. No response codes should be reused within the same question. | Yes  
`value` | For `SING`/`MULT`, leave blank if no scoring is desired. If scoring is desired, insert the numerical value assigned. For `TXT`, optionally provide a default pre-populated response. Not used for `DATE`. | No  
Like `TXT`, a `DATE` question takes exactly one entry in `responses`: set `name: "DATE"` and omit `value`, since scoring does not apply. In a note it renders as a date picker and accepts a calendar date in `YYYY-MM-DD` format.
###  Example Questionnaire Definition 
    ```yaml
    name: Example Name
    form_type: QUES
    code_system: LOINC
    code: QUES_EXAMPLE_NAME
    can_originate_in_charting: true
    prologue: This is an example of a structured assessment with single select, multiselect, free text, and date responses.
    questions:
      - content: "This is question #1"
        code_system: CPT
        code: H0005
        responses_code_system: INTERNAL
        responses_type: SING
        display_result_in_social_history_section: true
        responses:
          - name: "Single select response #1"
            code: QUES_EXAMPLE_NAME_Q1_A1
            value: "1"
          - name: "Single select response #2"
            code: QUES_EXAMPLE_NAME_Q1_A2
            value: "0"
          - name: "Single select response #3"
            code: QUES_EXAMPLE_NAME_Q1_A3
            value: "0"
      - content: "This is question #2"
        code_system: INTERNAL
        code: QUES_EXAMPLE_NAME_Q2
        responses_code_system: ICD-10
        responses_type: MULT
        display_result_in_social_history_section: true
        responses:
          - name: "Multi select response #1"
            code: F1910
            value: "0"
          - name: "Multi select response #2"
            code: QUES_EXAMPLE_NAME_Q1_A1
            value: "2"
          - name: "Multi select response #3"
            code: QUES_EXAMPLE_NAME_Q1_A2
            value: "0"
      - content: "This is question #3"
        code_system: INTERNAL
        code: QUES_EXAMPLE_NAME_Q3
        responses_code_system: INTERNAL
        responses_type: TXT
        display_result_in_social_history_section: true
        responses:
          - name: "Free text response"
            code: QUES_EXAMPLE_NAME_Q3_A1
            value: "This is a default pre-populated free text response."
      - content: "This is question #4"
        code_system: INTERNAL
        code: QUES_EXAMPLE_NAME_Q4
        responses_code_system: INTERNAL
        responses_type: DATE
        display_result_in_social_history_section: true
        responses:
          - name: "DATE"
            code: QUES_EXAMPLE_NAME_Q4_A1
    ```
###  Example Questionnaire with Conditional Logic (Branching) 
    ```yaml
    name: Branching Example
    form_type: QUES
    code_system: INTERNAL
    code: QUES_BRANCHING_EXAMPLE
    can_originate_in_charting: true
    prologue: This questionnaire demonstrates conditional logic with enabled_conditions and enabled_behavior.
    questions:
      - content: "Do you have any allergies?"
        code_system: INTERNAL
        code: QUES_BRANCH_Q1
        responses_code_system: INTERNAL
        responses_type: SING
        responses:
          - name: "Yes"
            code: QUES_BRANCH_Q1_YES
          - name: "No"
            code: QUES_BRANCH_Q1_NO
      - content: "Please describe your allergies"
        code_system: INTERNAL
        code: QUES_BRANCH_Q2
        responses_code_system: INTERNAL
        responses_type: TXT
        enabled_conditions:
          - question_code: QUES_BRANCH_Q1
            operator: "="
            value_code: QUES_BRANCH_Q1_YES
        responses:
          - name: "TXT"
            code: QUES_BRANCH_Q2_A1
      - content: "How severe are your allergies?"
        code_system: INTERNAL
        code: QUES_BRANCH_Q3
        responses_code_system: INTERNAL
        responses_type: SING
        enabled_behavior: all
        enabled_conditions:
          - question_code: QUES_BRANCH_Q1
            operator: "="
            value_code: QUES_BRANCH_Q1_YES
          - question_code: QUES_BRANCH_Q2
            operator: exists
        responses:
          - name: "Mild"
            code: QUES_BRANCH_Q3_MILD
          - name: "Moderate"
            code: QUES_BRANCH_Q3_MODERATE
          - name: "Severe"
            code: QUES_BRANCH_Q3_SEVERE
    ```
In this example:
  - **Q1** is always visible.
  - **Q2** only appears if Q1 is answered "Yes" (using `=` with `value_code`).
  - **Q3** only appears if Q1 is "Yes" **and** Q2 has been answered (using `enabled_behavior: all` with two conditions).
##  Load Questionnaire definition from YAML file 
You can use the `questionnaire_from_yaml` function from `canvas_sdk.questionnaires` within your plugin to load a questionnaire definition from a YAML file. The function takes the path to the YAML file as an argument and returns a dictionary containing the questionnaire definition.
    ```python
    def questionnaire_from_yaml(questionnaire_name: str, **kwargs):
        """Load a Questionnaire configuration from a YAML file.
        Args:
            questionnaire_name (str): The path to the questionnaire file, relative to the plugin package.
                If the path starts with a forward slash ("/"), it will be stripped during resolution.
            kwargs (Any): Additional keyword arguments.
        Returns:
            QuestionnaireConfig: The loaded Questionnaire configuration.
        Raises:
            FileNotFoundError: If the questionnaire file does not exist within the plugin's directory
                or if the resolved path is invalid.
            PermissionError: If the resolved path is outside the plugin's directory.
            ValidationError: If the questionnaire file does not conform to the JSON schema.
        """
    ```
----- END PAGE https://docs.canvasmedical.com/sdk/questionnaires/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/sandboxing-and-allowed-imports/
Plugins developed with the Canvas SDK operate within a sandboxed environment. This sandbox strictly limits access to the host operating system, filesystem, and database. This security measure is designed to mitigate risks associated with accidental misconfigurations or malicious activities, thereby safeguarding sensitive patient data.
##  Standard Library Modules 
The following Python standard library modules and their allowed imports are available within the sandbox:
#####  `__future__`
Provides access to features from future Python versions for backwards compatibility. [read more](https://docs.python.org/3/library/__future__.html)
  - `annotations`
#####  `abc`
Provides infrastructure for defining Abstract Base Classes (ABCs) to enforce interfaces and create structured inheritance hierarchies. [read more](https://docs.python.org/3/library/abc.html)
  - `ABC`
  - `abstractmethod`
#####  `base64`
Provides functions for encoding and decoding data in base64 format, commonly used for data transmission and storage. [read more](https://docs.python.org/3/library/base64.html)
  - `b64decode`
  - `b64encode`
#####  `collections`
Provides specialized container datatypes that extend beyond the built-in types like lists and dictionaries. [read more](https://docs.python.org/3/library/collections.html)
  - `Counter`
  - `defaultdict`
#####  `dataclasses`
This module provides a decorator and functions for automatically adding generated special methods such as **init**() and **repr**() to user-defined classes. [read more](https://docs.python.org/3/library/dataclasses.html)
  - `asdict`
  - `astuple`
  - `dataclass`
  - `field`
  - `Field`
  - `fields`
  - `InitVar`
  - `is_dataclass`
  - `make_dataclass`
  - `replace`
#####  `datetime`
Provides classes for working with dates and times, essential for medical applications that need to track appointment schedules and patient timelines. [read more](https://docs.python.org/3/library/datetime.html)
  - `date`
  - `datetime`
  - `time`
  - `timedelta`
  - `timezone`
  - `UTC`
#####  `dateutil`
Extends Python's datetime capabilities with more flexible date parsing and arithmetic. [read more](https://dateutil.readthedocs.io/en/stable/)
  - `relativedelta`
#####  `dateutil.relativedelta`
Provides relative time delta calculations for more complex date arithmetic operations. [read more](https://dateutil.readthedocs.io/en/stable/relativedelta.html)
  - `relativedelta`
#####  `decimal`
Provides precise decimal arithmetic for financial and scientific calculations where floating-point accuracy is critical. [read more](https://docs.python.org/3/library/decimal.html)
  - `Decimal`
#####  `defusedxml.ElementTree`
The defusedxml package contains several Python-only workarounds and fixes for denial of service and other vulnerabilities in Python's XML libraries. [read more](https://pypi.org/project/defusedxml/)
  - `fromstring`
#####  `enum`
Provides support for enumerations, useful for defining sets of named constants such as status codes or categories. [read more](https://docs.python.org/3/library/enum.html)
  - `Enum`
  - `StrEnum`
#####  `functools`
Provides utilities for higher-order functions and operations on callable objects. [read more](https://docs.python.org/3/library/functools.html)
  - `reduce`
  - `wraps`
#####  `hashlib`
Provides secure hash and message digest algorithms for data integrity verification and security purposes. [read more](https://docs.python.org/3/library/hashlib.html)
  - `sha256`
#####  `hmac`
Provides hash-based message authentication code (HMAC) functions for secure message authentication. [read more](https://docs.python.org/3/library/hmac.html)
  - `compare_digest`
  - `new`
#####  `html`
Provides functions for escaping and unescaping HTML entities. [read more](https://docs.python.org/3/library/html.html)
  - `escape`
  - `HTML`
  - `unescape`
#####  `http`
Provides HTTP status codes and related constants for web API development and HTTP response handling. [read more](https://docs.python.org/3/library/http.html)
  - `HTTPStatus`
#####  `json`
Provides functions for parsing and generating JSON data, essential for API communication and data serialization. [read more](https://docs.python.org/3/library/json.html)
  - `dumps`
  - `JSONDecodeError`
  - `loads`
#####  `operator`
Provides function equivalents of operators for functional programming and complex data operations. [read more](https://docs.python.org/3/library/operator.html)
  - `and_`
#####  `random`
Provides functions for generating random numbers and making random selections, useful for sampling and testing scenarios. [read more](https://docs.python.org/3/library/random.html)
  - `choices`
  - `randint`
  - `uniform`
#####  `re`
Provides regular expression matching operations for pattern matching and text processing. [read more](https://docs.python.org/3/library/re.html)
  - `compile`
  - `DOTALL`
  - `findall`
  - `fullmatch`
  - `IGNORECASE`
  - `match`
  - `search`
  - `split`
  - `sub`
#####  `string`
Provides string constants and template classes for string manipulation and formatting operations. [read more](https://docs.python.org/3/library/string.html)
  - `ascii_lowercase`
  - `digits`
#####  `time`
Provides time-related functions for measuring execution time and adding delays in processing. [read more](https://docs.python.org/3/library/time.html)
  - `sleep`
  - `time`
  - `time_ns`
#####  `traceback`
Provides functions for extracting, formatting, and printing stack traces. [read more](https://docs.python.org/3/library/traceback.html)
  - `format_exc`
#####  `typing`
Provides support for type hints and static type checking to improve code clarity and IDE support. [read more](https://docs.python.org/3/library/typing.html)
  - `Any`
  - `Callable`
  - `cast`
  - `ClassVar`
  - `Dict`
  - `Final`
  - `Iterable`
  - `List`
  - `Literal`
  - `NamedTuple`
  - `NotRequired`
  - `Optional`
  - `Pattern`
  - `Protocol`
  - `Sequence`
  - `Tuple`
  - `TYPE_CHECKING`
  - `Type`
  - `TypedDict`
  - `TypeGuard`
  - `Union`
#####  `urllib`
Provides modules for working with URLs, including URL parsing and manipulation. [read more](https://docs.python.org/3/library/urllib.html)
  - `parse`
#####  `urllib.parse`
Provides URL parsing utilities for breaking apart and constructing URLs and query strings. [read more](https://docs.python.org/3/library/urllib.parse.html)
  - `quote`
  - `unquote`
  - `urlencode`
#####  `uuid`
Provides functions for generating universally unique identifiers (UUIDs) for creating unique record identifiers. [read more](https://docs.python.org/3/library/uuid.html)
  - `uuid4`
  - `UUID`
#####  `zoneinfo`
Provides timezone support for handling datetime objects across different time zones. [read more](https://docs.python.org/3/library/zoneinfo.html)
  - `ZoneInfo`
##  Third-Party Modules 
The following third-party modules and their allowed imports are available within the sandbox:
#####  `arrow`
A human-friendly approach to creating, manipulating, formatting and converting dates and times. [read more](https://arrow.readthedocs.io/en/latest/)
  - `get`
  - `now`
  - `utcnow`
#####  `django.contrib.postgres.indexes`
Django's PostgreSQL-specific index types for advanced indexing strategies. [read more](https://docs.djangoproject.com/en/stable/ref/contrib/postgres/indexes/)
  - `GinIndex`
#####  `django.db`
Django's database module providing core database exceptions. [read more](https://docs.djangoproject.com/en/stable/ref/exceptions/#database-exceptions)
  - `IntegrityError`
#####  `django.db.models`
Django's database abstraction layer for defining database models and performing queries. [read more](https://docs.djangoproject.com/en/stable/topics/db/models/)
  - `Avg`
  - `BigIntegerField`
  - `BooleanField`
  - `CASCADE`
  - `Case`
  - `CharField`
  - `Count`
  - `DateField`
  - `DateTimeField`
  - `DecimalField`
  - `DO_NOTHING`
  - `Exists`
  - `F`
  - `FloatField`
  - `ForeignKey`
  - `Func`
  - `Index`
  - `IntegerField`
  - `JSONField`
  - `ManyToManyField`
  - `Max`
  - `Min`
  - `OneToOneField`
  - `OuterRef`
  - `Prefetch`
  - `Q`
  - `RowRange`
  - `SET_NULL`
  - `Subquery`
  - `Sum`
  - `TextField`
  - `UniqueConstraint`
  - `Value`
  - `ValueRange`
  - `When`
  - `Window`
#####  `django.db.models.expressions`
Django's database expressions for complex query operations and conditional logic. [read more](https://docs.djangoproject.com/en/stable/ref/models/expressions/)
  - `Case`
  - `Exists`
  - `OuterRef`
  - `Subquery`
  - `Value`
  - `When`
#####  `django.db.models.functions`
Django's database functions for common SQL operations and window functions. [read more](https://docs.djangoproject.com/en/stable/ref/models/database-functions/)
  - `Coalesce`
  - `CumeDist`
  - `DenseRank`
  - `FirstValue`
  - `Lag`
  - `LastValue`
  - `Lead`
  - `NthValue`
  - `Ntile`
  - `PercentRank`
  - `Rank`
  - `RowNumber`
  - `Trim`
#####  `django.db.models.query`
Django's QuerySet class for database query operations and result handling. [read more](https://docs.djangoproject.com/en/stable/ref/models/querysets/)
  - `Prefetch`
  - `QuerySet`
#####  `django.db.transaction`
Django's transaction management for atomic database operations. [read more](https://docs.djangoproject.com/en/stable/topics/db/transactions/)
  - `atomic`
  - `on_commit`
  - `on_rollback`
#####  `django.utils.functional`
Django's functional programming utilities including caching and lazy evaluation tools. [read more](https://docs.djangoproject.com/en/stable/ref/utils/)
  - `cached_property`
#####  `jwt`
A library for encoding and decoding JSON Web Tokens (JWT) for secure data transmission and authentication. [read more](https://pyjwt.readthedocs.io/en/stable/)
  - `decode`
  - `encode`
  - `ExpiredSignatureError`
  - `InvalidTokenError`
  - `PyJWKClient`
#####  `pydantic`
A data validation library using Python type annotations for parsing and validating data structures. [read more](https://docs.pydantic.dev/)
  - `BaseModel`
  - `ConfigDict`
  - `conint`
  - `constr`
  - `Field`
  - `RootModel`
  - `ValidationError`
#####  `rapidfuzz`
A fast string matching library for fuzzy string comparison and search operations. [read more](https://maxbachmann.github.io/RapidFuzz/)
  - `fuzz`
  - `process`
  - `utils`
##  Canvas SDK Modules 
All Canvas SDK modules are available for import and use within your plugins:
  - `canvas_sdk.caching`
  - `canvas_sdk.commands`
  - `canvas_sdk.effects`
  - `canvas_sdk.events`
  - `canvas_sdk.handlers`
  - `canvas_sdk.protocols`
  - `canvas_sdk.questionnaires`
  - `canvas_sdk.templates`
  - `canvas_sdk.utils`
  - `canvas_sdk.v1.data`
  - `canvas_sdk.value_set`
  - `canvas_sdk.views`
  - `logger`
##  Builtin Functions 
The following Python builtin functions are available within the sandbox:
  - `all`
  - `any`
  - `classmethod`
  - `dict`
  - `enumerate`
  - `extract_exc_frames`
  - `filter`
  - `getattr`
  - `hasattr`
  - `iter`
  - `list`
  - `map`
  - `max`
  - `min`
  - `next`
  - `property`
  - `reversed`
  - `staticmethod`
  - `sum`
  - `super`
  - `vars`
On top of those, the sandbox inherits RestrictedPython's safe builtins. That covers the basic types (`bool`, `bytes`, `complex`, `float`, `frozenset`, `int`, `set`, `slice`, `str`, `tuple`), the common functions (`abs`, `callable`, `chr`, `divmod`, `hash`, `hex`, `id`, `isinstance`, `issubclass`, `len`, `oct`, `ord`, `pow`, `range`, `repr`, `round`, `sorted`, `zip`), and most of the standard exception classes.
###  Builtins that are not available 
What you see above is the whole set. The sandbox works from an allow-list rather than a list of banned names, so any builtin not mentioned there raises `NameError` when your plugin runs — including builtins that future Python releases introduce.
These are the ones plugin authors reach for most often:
Not available | Use instead  
---|---  
`eval`, `exec`, `compile` | Write the logic directly. The sandbox cannot run code it never reviewed.  
`open`, `input` | Neither the filesystem nor a console is reachable from a plugin.  
`print` | `log` from `logger`. `print` is also a reserved name.  
`type` | `isinstance(x, SomeClass)` to test a type, `x.__class__.__name__` to read its name.  
`dir`, `globals`, `locals` | Nothing. Namespace introspection is a sandbox-escape route.  
`bytearray` | `bytes` for binary data.  
One group is easy to miss: the `OSError` subclasses, including `TimeoutError`, `ConnectionError`, `FileNotFoundError`, and `PermissionError`. Writing `except TimeoutError:` around an HTTP call raises `NameError`. Catch `OSError` instead — it is available, and it matches every one of them:
    ```python
    from canvas_sdk.utils import Http
    client = Http()
    try:
        response = client.get("https://example.com/api")
    except OSError:
        # OSError is the shared base class, so this catches TimeoutError,
        # ConnectionError, and the rest of the family.
        pass
    ```
##  Forbidden Constructs 
Beyond the import allow-list above, a few Python constructs compile under RestrictedPython but are rejected when your code runs in the sandbox. `canvas validate` catches these statically before you install, so you don't have to wait for a runtime failure on the instance.
Construct | Why it's rejected | Use instead  
---|---|---  
`setattr(obj, "x", value)` | Dynamic attribute assignment is blocked | Direct assignment: `obj.x = value`  
`delattr(obj, "x")` | Dynamic attribute deletion is blocked | `del obj.x`  
`bytearray(...)` | Not available in the sandbox | `bytes` for binary data  
`type(name, bases, dict)` | Dynamic class creation (3-argument `type`) is not available | Declare the class normally with `class …:`  
`obj.attr += v` | Augmented assignment to an attribute is rejected, including on classes you defined yourself | Explicit reassignment: `obj.attr = obj.attr + v`  
`d[k] += v` | Augmented assignment to a dict item, list item, or slice is rejected | Explicit reassignment: `d[k] = d[k] + v`  
Augmented assignment to a plain variable is fine — `count += 1`, `total *= 2`, and the rest of the `-=` / `*=` / `//=` / `%=` / `**=` / `&=` / `|=` / `^=` / `<<=` / `>>=` family all work. It is only the attribute and item forms above that are rejected, and both fail when the plugin is compiled, so you find out at install time rather than mid-request.
> **Warning:** `type` is not available in the sandbox _at all_ , including the one-argument `type(x)` form used to check an object's type — it raises `NameError: name 'type' is not defined`. Use `isinstance(x, SomeClass)` to test a type, or `x.__class__.__name__` to read its name. 
> **Info:** `@dataclass(frozen=True)` and `@dataclass(slots=True)` load and run fine in the sandbox — they are not forbidden. 
###  `extract_exc_frames()`
A sandbox-provided function that extracts frame information from the current exception's traceback. Must be called from within an `except` block. Returns an empty list if no exception is active.
Each frame exposes only safe attributes:
  - `filename` — the file path
  - `lineno` — the line number
  - `name` — the function name
Source code lines and local variables are not accessible.
    ```python
    from logger import log 
    try:
        raise Exception("some failed operation")
    except Exception:
        frames = extract_exc_frames()
        for frame in frames:
            log.info(f"{frame.filename}:{frame.lineno} in {frame.name}")
    ```
##  Runtime Restrictions 
The sandbox enforces the rules in this section every time an attribute is read or written, so they surface as an `AttributeError` while your plugin is running. That is what separates them from the forbidden constructs, which are rejected when your code is compiled and reported by `canvas validate` before you install. Nothing described here is caught until the code executes.
Throughout this section, **your plugin's code** means modules inside your own plugin package. **External code** means everything else — the Canvas SDK, the standard library, and third-party modules.
###  Reading attributes 
Attribute names that begin with an underscore are restricted, and the rule depends on where the object came from:
Object defined in | `_single_underscore` | `__dunder__`  
---|---|---  
Your plugin's code | Readable | Only names on the allow-list below  
External code | Blocked | Only names on the allow-list below  
The dunder allow-list is the same in both cases:
  - `__annotations__`
  - `__args__`
  - `__class__`
  - `__dict__`
  - `__eq__`
  - `__init__`
  - `__members__`
  - `__name__`
  - `__origin__`
  - `__traceback__`
Two of those return a restricted stand-in rather than the real object:
  - **`__class__`** on an object defined outside your plugin returns a read-only proxy that exposes only `__name__`. This is what prevents `__class__.__mro__` and `__class__.__subclasses__()` from being used to reach code outside the sandbox.
  - **`__traceback__`** returns a safe traceback exposing only `tb_frame`, `tb_lineno`, and `tb_next`. Its frame exposes only `f_code`, and that code object exposes only `co_filename` and `co_name`. Local and global variables are never reachable. `extract_exc_frames()` is the more convenient way to read a traceback.
Reading a plain attribute off an imported module is also limited to that module's entry in the allow-list at the top of this page. `json.dumps` works because `dumps` is listed under `json`; `json.tool` raises an `AttributeError`.
###  Reading items 
Subscripting with a string key that starts with an underscore is blocked on every object, including dictionaries you created yourself:
    ```python
    config = {"timeout": 30, "_internal": True}
    config["timeout"]    # fine
    config["_internal"]  # AttributeError
    ```
###  Writing attributes 
You can set attributes on modules that belong to your plugin. Setting an attribute on any other module is blocked.
For objects, whether a write is allowed depends on where the object's class was defined:
  - **Class defined in your plugin's code** — writable, including attributes that are not methods.
  - **Class defined in external code** — the write is blocked if any of the following is true: 
    - the name you are assigning through was brought in by an `import`
    - the attribute currently holds a callable, so the assignment would replace a method
    - the target is a dictionary and the key is a string starting with an underscore
    ```python
    class MyThing:
        """Defined in your plugin, so its instances are writable."""
        def __init__(self) -> None:
            self.count = 0
    thing = MyThing()
    thing.count = 1       # fine
    thing.label = "new"   # fine
    ```
###  Reserved names 
These four names cannot be used for a function, variable, class, or argument anywhere in your plugin:
  - `print`
  - `printed`
  - `builtins`
  - `breakpoint`
`print` is among them, so use the SDK logger for output:
    ```python
    from logger import log
    log.info("plugin started")
    ```
###  Introspection attributes 
The attributes the `inspect` module relies on are unavailable, because they expose frames, globals, and raw bytecode:
`co_code`, `cr_await`, `cr_code`, `cr_frame`, `cr_origin`, `f_back`, `f_builtins`, `f_code`, `f_generator`, `f_globals`, `f_locals`, `f_trace`, `gi_code`, `gi_frame`, `gi_yieldfrom`, `tb_frame`, `tb_next`
The safe traceback wrappers under reading attributes are the one exception: they re-expose `tb_frame`, `tb_next`, and `f_code` through an explicit allow-list, stripped down to the fields listed there.
###  String formatting 
The `format` and `format_map` methods of `str` are not available. Use an f-string or the `%` operator instead:
    ```python
    name = "Canvas"
    greeting = f"Hello {name}"       # fine
    greeting = "Hello %s" % name     # fine
    greeting = "Hello {}".format(name)  # NotImplementedError
    ```
###  `__exports__`
Some SDK objects declare an `__exports__` attribute listing exactly which attribute names may be read from them. Where it is present it takes precedence over the other rules in this section: names in the list are readable, and anything else raises an `AttributeError`.
> **Info:** These rules are enforced by `plugin_runner/sandbox.py` in the [Canvas Plugins repository](https://github.com/canvas-medical/canvas-plugins), which is the authoritative reference if you hit a restriction that isn't described here. 
##  Requesting Additional Imports 
If there is a library or function not on this list that you wish to import in your plugin, reach out on the [Canvas developer forum](https://github.com/canvas-medical/canvas-plugins/discussions). Additional imports can often be added after a security review.
The allowed imports are defined in the [Canvas Plugins repository](https://github.com/canvas-medical/canvas-plugins/blob/main/plugin_runner/sandbox.py) and are regularly updated to support common development needs while maintaining security.
##  Policy on Vendor-Specific Libraries: 
The current policy strongly discourages the inclusion of vendor-specific libraries. Introducing such libraries presents several challenges:
  - Vendor Prioritization: It risks implicitly favoring one vendor over others, which can be problematic in a multi-vendor ecosystem.
  - Dependency Bloat: Incorporating libraries for each vendor within a specific category (e.g., AI model providers like OpenAI, Anthropic) leads to a significant increase in overall dependencies.
##  Technical Implications of Excessive Dependencies: 
Adding a multitude of vendor-specific libraries can result in:
  - Increased Memory Usage: Each additional library contributes to the application's memory footprint.
  - Dependency Conflicts: Different libraries may require different versions of shared dependencies, leading to versioning conflicts and system instability.
Given these considerations, the platform maintains a strict and judicious approach to approving and incorporating external libraries or imports.
----- END PAGE https://docs.canvasmedical.com/sdk/sandboxing-and-allowed-imports/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/secrets/
Canvas provides a secure key-value store that lets your plugins access configuration data — including sensitive secrets such as API tokens — without hardcoding values into source files. Configuration values are declared in your plugin's `CANVAS_MANIFEST.json` as **variables** , set at install time or through the Admin UI, and read from `self.secrets` at runtime.
Each variable can be marked **sensitive** (treated like a secret: not displayed in admin or CLI listings) or **non-sensitive** (a regular configuration value, displayed in plaintext for verification). All variable values are write-only through the CLI and API.
###  Declaring Variables in `CANVAS_MANIFEST.json`
Variables are declared in your plugin's `CANVAS_MANIFEST.json` file under the top-level `variables` field. Each variable is an object with a `name` and an optional `sensitive` flag (defaults to `false`). Variables marked as `sensitive: true` are write-only and behave like secrets. These declared variables become available for configuration in the Canvas Admin UI when the plugin is installed.
    ```json
    {
      "sdk_version": "0.1.4",
      "plugin_version": "0.0.1",
      "name": "live_notifications",
      "description": "Edit the description in CANVAS_MANIFEST.json",
      "components": {
        "handlers": [
          {
            "class": "live_notifications.handlers.my_handler:Handler",
            "description": "A handler that does xyz..."
          }
        ]
      },
      "variables": [
        {"name": "API_TOKEN", "sensitive": true},
        {"name": "WEBHOOK_URL"},
        {"name": "LOG_LEVEL", "sensitive": false}
      ],
      "tags": {}
    }
    ```
Field | Type | Required | Description  
---|---|---|---  
`name` | string | Yes | The variable name used in your plugin code  
`sensitive` | boolean | No | When `true`, marks the variable as sensitive for display purposes (default: `false`)  
####  Legacy `secrets` array (deprecated) 
The flat `secrets` array is still accepted for backwards compatibility:
    ```json
    "secrets": ["API_TOKEN"]
    ```
It emits a deprecation warning during `canvas validate-manifest` and is mapped internally to `variables` entries with `sensitive: false`. To preserve sensitive treatment, migrate to the `variables` schema with `sensitive: true` and re-install the plugin.
> **Warning:** **Deprecation Notice:** The legacy `secrets` array format is deprecated. Use the `variables` format shown above instead. The legacy format will continue to work but displays a deprecation warning during `canvas validate-manifest`. 
> ⚠️ **Pre-existing values default to non-sensitive.** Any plugin secret that existed before Canvas 1.305.0 — or any value configured via the legacy `secrets:` array — is stored with `sensitive: false`. It will appear in plain text in the Admin UI until the owning plugin is migrated to the `variables` schema with `sensitive: true` and re-installed.
###  Configuring Variables from the CLI 
Set values during install or update them later. Use `--variable` for non-sensitive values and `--secret` for sensitive values; both flags accept `KEY=value` pairs.
Provide values during install:
    ```console
    $ canvas install <plugin_name> --secret API_TOKEN=your_api_token_value --variable LOG_LEVEL=info
    ```
Update values on an installed plugin:
    ```console
    $ canvas config set <plugin_name> API_TOKEN=abc123 LOG_LEVEL=warn
    ```
Pass multiple values by repeating the flag:
    ```console
    $ canvas install <plugin_name> \
      --secret API_TOKEN=abc123 \
      --secret WEBHOOK_SECRET=xyz \
      --variable LOG_LEVEL=info
    ```
####  Listing configured values 
Run `canvas config list <plugin_name>` to see which variables are configured for a plugin. Each variable is rendered as `[set]` or `[not set]`, with a `(sensitive)` annotation for sensitive variables. Values themselves are never displayed in the listing.
    ```console
    $ canvas config list my_plugin
      API_TOKEN  [set]  (sensitive)
      LOG_LEVEL  [not set]
    ```
To read a value, use the Django Admin UI (access is gated by managing-user permissions).
> _The`--variable` flag, `canvas config list` sensitive marking, and Admin UI masking require Canvas CLI 0.146.0 or newer. Upgrade with `pip install --upgrade canvas`._
###  Configuring Variables in the Admin UI 
After install you can also set values through the Admin interface.
Navigation path: `Home` › `Plugin_IO` › `Plugins` › `(your plugin)`
Or, go directly to:
    https://<your_canvas_instance>/admin/plugin_io/plugin/<plugin_id>/change/
On this page, you will find input fields for each variable defined in your manifest. Sensitive values display as `SENSITIVE` and are no longer rendered in the form HTML — submit a new value to overwrite, or leave the field blank to keep the existing value. Non-sensitive values display their current value and can be edited inline.
![Setting plugin variables](/assets/images/sdk/secrets/plugins_secrets_settings_with_permissions.png)
Sensitive variables can be protected by managing user permissions. Only users explicitly assigned as "managing users" for a plugin can view or modify its sensitive variables (as well as other sensitive settings like the plugin package file download link). Other users can see basic plugin details and enable or disable plugins, but they will not be able to access or change sensitive variable values. To add or remove managing users for a plugin, use the "Managing users" section on the plugin detail page in the Admin UI. This ensures that sensitive configuration, such as API tokens, remains visible only to authorized personnel.
###  Accessing Variables in Your Plugin 
All variables — sensitive and non-sensitive alike — are exposed to your plugin code through `self.secrets`, a Python dictionary keyed by variable name:
    ```python
    from canvas_sdk.handlers import BaseHandler
    from canvas_sdk.effects import Effect
    class MyHandler(BaseHandler):
        def compute(self) -> list[Effect]:
            api_token = self.secrets["API_TOKEN"]
            webhook_url = self.secrets["WEBHOOK_URL"]
            log_level = self.secrets["LOG_LEVEL"]
            ...
    ```
This access pattern is unchanged from earlier Canvas versions, so migrating a plugin from the legacy `secrets:` array to the new `variables:` schema requires no handler code changes.
----- END PAGE https://docs.canvasmedical.com/sdk/secrets/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/sso/
Canvas supports SAML 2.0 single sign-on, and exposes hooks that let plugins participate in the post-login flow. Use these hooks to capture data from the identity provider's SAML response, or to send a user to a custom landing page after they authenticate.
Both hooks fire after the SAML assertion has been validated and the user has been logged in. The user's identity is available as [`self.event.actor`](/sdk/events/#event-actor); the SAML response data is available in `self.event.context`.
##  Hooks at a glance 
Hook | Type | Purpose  
---|---|---  
`SSO__PROCESS_ADDITIONAL_REQUEST_DATA` | Event | Read-only access to the SAML response so a plugin can capture IdP attributes (group memberships, employee ID, etc.).  
`SSO__GET_POST_LOGIN_REDIRECT` | Event | Override the URL the user lands on after SSO login.  
`REDIRECT_CONTEXT` | Effect | The effect returned from `SSO__GET_POST_LOGIN_REDIRECT` to set the post-login destination.  
##  `SSO__PROCESS_ADDITIONAL_REQUEST_DATA`
Fires once per successful SAML login, immediately after the user is authenticated. The event is **read-only** : any effects a handler returns are discarded. Use it for side-effects such as syncing IdP attributes onto a [`Staff`](/sdk/data-staff/) record, writing an audit log, or notifying an external system that a user signed in.
###  Context 
Key | Type | Description  
---|---|---  
`session_info` | dict | The validated SAML response data from pysaml2. Common keys include `name_id` (the SAML NameID), `issuer` (IdP entity ID), `ava` (a dict of IdP-supplied user attributes keyed by attribute name with list-valued entries), `session_index`, `not_on_or_after`, and `authn_info`.  
###  Target 
The Canvas user that just logged in. `self.event.target` resolves to a [`Staff`](/sdk/data-staff/) or [`Patient`](/sdk/data-patient/) via `self.event.actor.instance.person_subclass`.
###  Example 
    ```python
    import json
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers.base import BaseHandler
    from logger import log
    class CaptureSSOAttributes(BaseHandler):
        """Log the IdP-supplied group memberships for every SSO login."""
        RESPONDS_TO = EventType.Name(EventType.SSO__PROCESS_ADDITIONAL_REQUEST_DATA)
        def compute(self):
            session_info = json.loads(self.event.context)["session_info"]
            groups = session_info.get("ava", {}).get("groups", [])
            log.info(f"SSO login for {session_info['name_id']} with groups={groups}")
            return []
    ```
##  `SSO__GET_POST_LOGIN_REDIRECT`
Fires once per successful SAML login, right before Canvas decides where to send the user. A plugin that returns a `REDIRECT_CONTEXT` effect sets the destination URL; if no plugin returns one (or the value is falsy), Canvas falls back to its default post-login redirect.
Only the first `REDIRECT_CONTEXT` effect returned is used. If multiple plugins register a handler, ordering between them is not guaranteed — coordinate across plugins to avoid conflicting redirects.
###  Context 
Key | Type | Description  
---|---|---  
`relay_state` | str | The SAML `RelayState` value from the original login request, if any. Often used by IdPs to encode a "deep link" — the URL the user was trying to reach before they were bounced to the IdP.  
`session_info` | dict | Same shape as for `SSO__PROCESS_ADDITIONAL_REQUEST_DATA`.  
###  Target 
Same as `SSO__PROCESS_ADDITIONAL_REQUEST_DATA` — the Canvas user that just logged in.
###  Example 
    ```python
    import json
    from canvas_generated.messages.effects_pb2 import EffectType
    from canvas_sdk.effects import Effect
    from canvas_sdk.events import EventType
    from canvas_sdk.handlers.base import BaseHandler
    class RouteSSOByGroup(BaseHandler):
        """Send admins to the admin app and everyone else to the schedule."""
        RESPONDS_TO = EventType.Name(EventType.SSO__GET_POST_LOGIN_REDIRECT)
        def compute(self):
            session_info = json.loads(self.event.context)["session_info"]
            groups = session_info.get("ava", {}).get("groups", [])
            if "canvas-admins" in groups:
                url = "/admin/"
            else:
                url = "/schedule/"
            return [
                Effect(
                    type=EffectType.REDIRECT_CONTEXT,
                    payload=json.dumps({"url": url}),
                )
            ]
    ```
##  `REDIRECT_CONTEXT`
The effect that carries the post-login URL back to Canvas. It is only meaningful when returned from a `SSO__GET_POST_LOGIN_REDIRECT` handler.
###  Payload 
Key | Type | Description  
---|---|---  
`url` | str | The URL Canvas should redirect the user to after SSO login. Relative paths (e.g. `/schedule/`) and absolute URLs are both accepted.  
There is no SDK helper class for this effect — construct it directly with `Effect(type=EffectType.REDIRECT_CONTEXT, payload=json.dumps({"url": ...}))` as shown in the example above.
----- END PAGE https://docs.canvasmedical.com/sdk/sso/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/testing-utils/
Canvas SDK provides a streamlined testing environment and local database support to help developers test their plugins with real data.
This guide covers two key capabilities:
  1. Plugin testing with pytest — `canvas[test-utils]` introduces a set of tools and dependencies for writing database-backed tests.
  2. Database seeding in CLI — populate or reset your local database when running plugins locally.
* * *
##  Project Structure 
The recommended layout for plugin development is now scaffolded automatically by the `canvas init` command. This structure ensures compatibility with Canvas testing tools and CLI features, and it's strongly encouraged for all new plugin projects. We also recommend to use [`uv`](https://docs.astral.sh/uv/getting-started/installation/) for managing dependencies.
    ```bash
    plugin-folder/
    ├── my_plugin/
    │   └── ...
    ├── tests/
    │   ├── __init__.py
    │   └── test_example.py
    └── pyproject.toml
    ```
##  Plugin Testing with Pytest 
Installing the `test-utils` extra provides everything needed to test plugins with pytest.
###  Features 
  - `pytest` with `factoryboy` style fixtures
  - Each test is wrapped in a transaction and rolled back automatically.
  - Use actual models or prebuilt factories to generate test data.
  - No need for mocks or fake database layers.
###  Installation 
Add the following to your `pyproject.toml`:
    ```toml
    [project]
    name = "canvas-plugins"
    version = "0.1.0"
    requires-python = ">=3.11,<3.13"
    dependencies = [
        "canvas[test-utils]",
        # other dependencies...
    ]
    ```
or install directly via `uv`:
    ```bash
    uv add "canvas[test-utils]"
    ```
You can now run your test suite using:
    ```bash
    uv run pytest
    ```
###  Using Factories 
Factories simplify test data creation and follow the [`factory_boy`](https://factoryboy.readthedocs.io/en/stable/) pattern.
    ```python
    from canvas_sdk.test_utils.factories import PatientFactory
    def test_factory() -> None:
        patient = PatientFactory.create()
        assert patient.id is not None
    ```
###  Using Models Directly 
You can also create records manually using the model classes:
    ```python
    from canvas_sdk.v1.data.discount import Discount
    def test_model() -> None:
        Discount.objects.create(
            name="10%",
            adjustment_group="30",
            adjustment_code="CO",
            discount=0.10,
        )
        assert Discount.objects.first().pk is not None
    ```
> Whether using a factory or model directly, your test will run inside a transaction and automatically roll back at the end.
###  Available Factories 
All factories are available from:
    ```python
    from canvas_sdk.test_utils import factories
    ```
Developers are encouraged to add new factories for their own models, and to submit PRs with contributions.
At this time, Canvas has factory support for the following models:
  - Claim
  - Claim Diagnosis Code
  - Facility
  - Medication History
  - Note
  - Organization
  - Patient
  - PracticeLocation
  - ProtocolCurrent
  - Staff
  - User
A complete list of available factories is located [here](https://github.com/canvas-medical/canvas-plugins/tree/main/canvas_sdk/test_utils/factories).
##  Local DB Seeding via `run-plugin`
You can run plugins locally with full access to the database using the CLI. Two options are available:
Option | Description  
---|---  
`--db-seed-file` | Path to a Python file that populates your database. _Warning_ : It resets the database before running the seed file.  
`--reset-db` | Clears and recreates the database before running the plugin  
###  Seed the Database 
To seed your plugin's database with test data:
    ```bash
    canvas run-plugin my_plugin --db-seed-file ./seed.py
    ```
Example `seed.py`:
    ```python
    from canvas_sdk.test_utils.factories import PatientFactory
    from canvas_sdk.v1.data.discount import Discount
    PatientFactory.create(first_name="Seeded", last_name="Patient")
    Discount.objects.create(
        name="20%",
        adjustment_group="X",
        adjustment_code="Y",
        discount=0.20
    )
    ```
###  Reset the Database 
To reset the database before running a plugin:
    ```bash
    canvas run-plugin my_plugin --reset-db
    ```
This will remove the existing database (if present) and recreate a clean version.
###  Simulate Events 
After your database is set up, simulate incoming events with:
    ```bash
    canvas emit
    ```
Run `canvas emit --help` for more info.
----- END PAGE https://docs.canvasmedical.com/sdk/testing-utils/


----- BEGIN PAGE https://docs.canvasmedical.com/sdk/utils/
##  Making requests with Http 
The Canvas SDK offers a helper class for completing HTTP calls.
    ```python
    from canvas_sdk.utils import Http
    http = Http()
    ```
###  get 
Sends a GET request.
**Parameters** :
Name | Type | Required | Description  
---|---|---|---  
`url` | _string_ | `true` | The url of the request.  
`headers` | _dict_ | `false` | The headers to include in the request.  
**Example** :
    ```python
    from canvas_sdk.utils import Http
    http = Http()
    http.get("https://my-url.com/", headers={"Authorization": f"Bearer token"})
    ```
###  post 
Sends a POST request.
**Parameters** :
Name | Type | Required | Description  
---|---|---|---  
`url` | _string_ | `true` | The url of the request.  
`headers` | _dict_ | `false` | The headers to include in the request.  
`json` | _dict_ | `false` | The json to include in the request.  
`data` | _dict_ or _string_ | `false` | The data to include in the request.  
**Example** :
    ```python
    from canvas_sdk.utils import Http
    http = Http()
    http.post(
        "https://my-url.com/",
        headers={"Authorization": f"Bearer token"},
        json={"post": "json"},
        data="this-is-my-data"
    )
    ```
###  put 
Sends a PUT request.
**Parameters** :
Name | Type | Required | Description  
---|---|---|---  
`url` | _string_ | `true` | The url of the request.  
`headers` | _dict_ | `false` | The headers to include in the request.  
`json` | _dict_ | `false` | The json to include in the request.  
`data` | _dict_ or _string_ | `false` | The data to include in the request.  
**Example** :
    ```python
    from canvas_sdk.utils import Http
    http = Http()
    http.put(
        "https://my-url.com/",
        headers={"Authorization": f"Bearer token"},
        json={"put": "json"},
        data="this-is-my-data"
    )
    ```
###  patch 
Sends a PATCH request.
**Parameters** :
Name | Type | Required | Description  
---|---|---|---  
`url` | _string_ | `true` | The url of the request.  
`headers` | _dict_ | `false` | The headers to include in the request.  
`json` | _dict_ | `false` | The json to include in the request.  
`data` | _dict_ or _string_ | `false` | The data to include in the request.  
**Example** :
    ```python
    from canvas_sdk.utils import Http
    http = Http()
    http.patch(
        "https://my-url.com/",
        headers={"Authorization": f"Bearer token"},
        json={"patch": "json"},
        data="this-is-my-data"
    )
    ```
##  Making concurrent requests with Http 
There is an interface for executing HTTP requests in parallel.
The `batch_requests` method will execute the requests in parallel using multithreading, and return once all the requests have completed.
The first parameter to the method is an iterable of `BatchableRequest` objects. These can be created with the following helper functions:
    batch_get
    batch_post
    batch_put
    batch_patch
The parameters that these helper functions accept match what the corresponding single-request methods accept.
The `timeout` parameter allows for specifying a timeout value in seconds; if a request has not completed before the timeout value, an error will be returned for that request. The maximum allowed value for `timeout` is 30 seconds. If `timeout` is not specified, it will be set to the maximum value.
The return value will be a list of responses to the requests. The order of the return value will correspond to the order of the provided requests.
**Parameters** :
Name | Type | Required | Description  
---|---|---|---  
`batch_requests` | _Iterable[BatchableRequest]_ | `true` | The list of batched requests.  
`timeout` | _integer_ | `false` | The timeout value in seconds.  
**Example** :
    ```python
    from canvas_sdk.utils import Http, batch_get, batch_post, batch_put, batch_patch
    http = Http()
    requests = [
        batch_get("https://my-url.com/", headers={"Authorization": f"Bearer token"}),
        batch_post("https://my-url.com/", headers={"Authorization": f"Bearer token"}, json={"post": "json"}),
        batch_put("https://my-url.com/", headers={"Authorization": f"Bearer token"}, data="this-is-my-data"),
        batch_patch("https://my-url.com/", headers={"Authorization": f"Bearer token"}, json={"patch": "json"})
    ]
    responses = http.batch_requests(requests, timeout=10)
    ```
##  Generating PDFs 
Plugin authors can generate PDFs using the `pdf_generator` client. There are two approaches: generating from a URL that serves HTML, or generating directly from an HTML string. Both methods upload the resulting PDF to S3 and return a presigned URL.
    ```python
    from canvas_sdk.utils.pdf import pdf_generator
    ```
The client exposes only `from_url` and `from_html`. Direct HTTP methods (get, post, put, patch) are not available.
###  from_url 
Generates a PDF from a URL that returns HTML. The service fetches the HTML from the given URL, converts it to PDF, uploads it to S3, and returns a presigned URL.
**Parameters** :
Name | Type | Required | Description  
---|---|---|---  
`print_url` | _string_ | `true` | The path to the HTML endpoint.  
`auth` | _PdfAuthRequest_ | `false` | Credentials forwarded to the PDF service so it can fetch authenticated endpoints.  
**Returns** : `PdfUrlResponse | None` — `None` if PDF generation failed.
When using `from_url`, the PDF service fetches the HTML from your endpoint directly. If that endpoint requires authentication, the SimpleAPI serving the HTML should use [`BasicAuthMixin`](/sdk/handlers-simple-api-http/#authentication-mixins) and pass the credentials via `PdfAuthRequest`.
**Example** :
    ```python
    from canvas_sdk.utils.pdf import PdfAuthRequest, pdf_generator
    # The PDF service will fetch this endpoint to get the HTML.
    # Because the endpoint uses BasicAuthMixin, we pass credentials
    # so the service can authenticate on our behalf.
    auth = PdfAuthRequest(
        username="user",
        password="password",
    )
    response = pdf_generator.from_url(
        print_url="plugin-io/api/my_plugin/printout/html?note_uuid=abc-123",
        auth=auth,
    )
    if response and response.url:
        # response.url is a presigned S3 URL to the generated PDF
        pdf_url = response.url
    ```
###  from_html 
Generates a PDF from a raw HTML string. Use this when you already have the HTML content and don't need the service to fetch it from a URL.
**Parameters** :
Name | Type | Required | Description  
---|---|---|---  
`content` | _string_ | `true` | The HTML content to convert.  
**Returns** : `PdfUrlResponse | None` — `None` if PDF generation failed.
**Example** :
    ```python
    from canvas_sdk.templates import render_to_string
    from canvas_sdk.utils.pdf import pdf_generator
    html = render_to_string("templates/note_printout.html", {
        "patient_name": "Jane Doe",
        "note_type": "Office Visit",
    })
    response = pdf_generator.from_html(content=html)
    if response and response.url:
        pdf_url = response.url
    ```
###  PdfUrlResponse 
Both methods return a `PdfUrlResponse` on success, or `None` on failure.
Attribute | Type | Description  
---|---|---  
`url` | _string_ | Presigned S3 URL to the generated PDF.  
###  Choosing between the two methods 
Use case | Method  
---|---  
Plugin serves an HTML page via SimpleAPI that requires authentication | `from_url` with `PdfAuthRequest`  
You already have the HTML string in memory (e.g. from `render_to_string`) | `from_html`  
##  Making requests to the Ontologies service 
Plugin authors can make requests to our Ontologies service using the `ontologies_http` wrapper.
In addition to the `json()` method of the response, which you'll use to access the response itself, you can also access the `status_code` to verify that the request was succcessful.
> **Use these endpoints for chart-parity lookups.** The medication and allergen search endpoints below back the autocompletes providers use in the chart. Use them — not the FHIR `/Medication` or `/Allergen` search endpoints — when you need a code that will resolve when used in a command. FHIR `/Medication` and `/Allergen` search different, narrower interoperability catalogs and can return codes the chart can't resolve (or omit ones it can); a code returned by the endpoints below resolves consistently with what the picker shows.
> **Tip:** The Medical Software Foundation's [`coding_lookup`](https://github.com/Medical-Software-Foundation/canvas/tree/main/data-migrations/data_migrations/plugins/coding_lookup) reference plugin wraps the medication and allergen lookups below as ready-made SimpleAPI endpoints (`/medication_search`, `/allergy_search`) returning a clean `{count, results}` shape. Install it as-is, or use it as a template for calling `ontologies_http` from your own plugin.
###  Searching for medications 
Plugin authors can search for medications by NDC code, RxNorm RXCUI, FDB code, or full-text search.
####  `fdb_code`
Elsewhere in the SDK there are commands that take an `fdb_code` or `new_fdb_code`, some examples being [`AdjustPrescriptionCommand`](/sdk/commands/#adjustprescription), [`MedicationStatementCommand`](/sdk/commands/#medicationstatement), [`PrescribeCommand`](/sdk/commands/#prescribe), and [`RefillCommand`](/sdk/commands/#refill). The value to be sent as the `fdb_code` is returned in the search payloads below as the `med_medication_id`.
####  `GET /fdb/grouped-medication/` — text and RxNorm search 
**Used by:** [Prescribe](/sdk/commands/#prescribe), [MedicationStatement](/sdk/commands/#medicationstatement), and [Refill](/sdk/commands/#refill) (`fdb_code`), and [Adjust Prescription](/sdk/commands/#adjustprescription) (`new_fdb_code`).
Search the medication catalog by full-text query or exact RxNorm RXCUI. Query parameters:
Parameter | Type | Description  
---|---|---  
`search` | string | Full-text search over the medication name, description, and synonyms.  
`rxnorm_rxcui` | string or int | Match a specific RxNorm RXCUI.  
    ```python
    from urllib.parse import urlencode
    from canvas_sdk.utils.http import ontologies_http
    # full-text search of the medication name, description, and synonyms
    response_json = ontologies_http.get_json(
        f"/fdb/grouped-medication/?{urlencode({'search': 'tylenol'})}"
    ).json()
    # search by a specific RxNorm RXCUI (same response shape)
    response_json = ontologies_http.get_json(
        f"/fdb/grouped-medication/?{urlencode({'rxnorm_rxcui': 313782})}"
    ).json()
    ```
The response contains a `results` list; each entry looks like:
    ```json
    {
      "results": [
        {
          "description_and_quantity": "Athenol 325 mg tablet",
          "med_medication_id": 436095,
          "search_rank": 0.082745634,
          "search_terms": "Athenol 325 mg tablet|ACETAMINOPHEN ORAL|...|TYLENOL|...",
          "med_medication_description": "Athenol 325 mg tablet",
          "clinical_quantities": [
            {
              "erx_quantity": "1.0000000",
              "representative_ndc": "11822317640",
              "clinical_quantity_description": "tablet",
              "erx_ncpdp_script_quantity_qualifier_code": "C48542",
              "erx_ncpdp_script_quantity_qualifier_description": "Tablet"
            }
          ],
          "etc_path_id": [3645, 574, 578, 577],
          "etc_path_name": [
            "Analgesic, Anti-inflammatory or Antipyretic",
            "Analgesic, Anti-inflammatory or Antipyretic - Non-Opioid",
            "Analgesic or Antipyretic Non-Opioid and Combinations",
            "Analgesic or Antipyretic Non-Opioid"
          ],
          "rxnorm_rxcui": "313782"
        }
      ]
    }
    ```
Each result's `clinical_quantities` array supplies the values for a [`ClinicalQuantity`](/sdk/commands/#clinicalquantity) on prescribe commands — `representative_ndc`, `erx_ncpdp_script_quantity_qualifier_code` → `ncpdp_quantity_qualifier_code`, and `clinical_quantity_description` → `description`.
####  `GET /fdb/grouped-medication/{med_medication_id}/` — look up by FDB code 
Fetch one or more medications by FDB code. Pass a single `med_medication_id`, or a comma-separated list of ids, as the path segment.
Parameter | Type | Description  
---|---|---  
`med_medication_id` (path) | int, or comma-separated ints | One or more FDB medication ids.  
    ```python
    from canvas_sdk.utils.http import ontologies_http
    # single FDB code
    response_json = ontologies_http.get_json("/fdb/grouped-medication/123456/").json()
    # multiple FDB codes
    med_medication_ids = ["123456", "123457"]
    response_json = ontologies_http.get_json(
        f"/fdb/grouped-medication/{','.join(med_medication_ids)}/"
    ).json()
    ```
Returns the same full `results` shape as the text/RxNorm search above — each entry includes every field, including `clinical_quantities`:
    ```json
    {
      "results": [
        {
          "description_and_quantity": "Athenol 325 mg tablet",
          "med_medication_id": 123456,
          "search_terms": "Athenol 325 mg tablet|ACETAMINOPHEN ORAL|...|TYLENOL|...",
          "med_medication_description": "Athenol 325 mg tablet",
          "clinical_quantities": [
            {
              "erx_quantity": "1.0000000",
              "representative_ndc": "11822317640",
              "clinical_quantity_description": "tablet",
              "erx_ncpdp_script_quantity_qualifier_code": "C48542",
              "erx_ncpdp_script_quantity_qualifier_description": "Tablet"
            }
          ],
          "etc_path_id": [3645, 574, 578, 577],
          "etc_path_name": [
            "Analgesic, Anti-inflammatory or Antipyretic",
            "Analgesic, Anti-inflammatory or Antipyretic - Non-Opioid",
            "Analgesic or Antipyretic Non-Opioid and Combinations",
            "Analgesic or Antipyretic Non-Opioid"
          ],
          "rxnorm_rxcui": "313782"
        }
      ]
    }
    ```
####  `GET /fdb/ndc-to-medication/{ndc}/` — look up by NDC 
Resolve a single NDC to its medication.
Parameter | Type | Description  
---|---|---  
`ndc` (path) | string | The NDC to resolve.  
    ```python
    from canvas_sdk.utils.http import ontologies_http
    response_json = ontologies_http.get_json("/fdb/ndc-to-medication/76420037215/").json()
    ```
The response is a single medication object:
    ```json
    {
      "description_and_quantity": "Aphen 325 mg tablet",
      "med_medication_id": 572345,
      "search_terms": "Aphen 325 mg tablet|APHEN 325 MG TABLET|ACETAMINOPHEN 325 MG TABLET",
      "med_medication_description": "Aphen 325 mg tablet",
      "clinical_quantities": [
        {
          "erx_quantity": "1.0000000",
          "representative_ndc": "76420037215",
          "clinical_quantity_description": "tablet",
          "erx_ncpdp_script_quantity_qualifier_code": "C48542",
          "erx_ncpdp_script_quantity_qualifier_description": "Tablet"
        }
      ],
      "etc_path_id": [3645, 574, 578, 577],
      "etc_path_name": [
        "Analgesic, Anti-inflammatory or Antipyretic",
        "Analgesic, Anti-inflammatory or Antipyretic - Non-Opioid",
        "Analgesic or Antipyretic Non-Opioid and Combinations",
        "Analgesic or Antipyretic Non-Opioid"
      ],
      "rxnorm_rxcui": "313782"
    }
    ```
####  `GET /fdb/ndcs-to-medications/{ndcs}/` — look up by multiple NDCs 
Resolve several NDCs at once. Pass a comma-separated list of NDCs as the path segment.
Parameter | Type | Description  
---|---|---  
`ndcs` (path) | comma-separated strings | The NDCs to resolve.  
    ```python
    from canvas_sdk.utils.http import ontologies_http
    response_json = ontologies_http.get_json(
        "/fdb/ndcs-to-medications/76420037215,11822317640/"
    ).json()
    ```
The response is a dictionary keyed by NDC; each value is the full medication object (same shape as the single-NDC lookup above), including `clinical_quantities`:
    ```json
    {
      "76420037215": {
        "description_and_quantity": "Aphen 325 mg tablet",
        "med_medication_id": 572345,
        "search_terms": "Aphen 325 mg tablet|APHEN 325 MG TABLET|ACETAMINOPHEN 325 MG TABLET",
        "med_medication_description": "Aphen 325 mg tablet",
        "clinical_quantities": [
          {
            "erx_quantity": "1.0000000",
            "representative_ndc": "76420037215",
            "clinical_quantity_description": "tablet",
            "erx_ncpdp_script_quantity_qualifier_code": "C48542",
            "erx_ncpdp_script_quantity_qualifier_description": "Tablet"
          }
        ],
        "etc_path_id": [3645, 574, 578, 577],
        "etc_path_name": [
          "Analgesic, Anti-inflammatory or Antipyretic",
          "Analgesic, Anti-inflammatory or Antipyretic - Non-Opioid",
          "Analgesic or Antipyretic Non-Opioid and Combinations",
          "Analgesic or Antipyretic Non-Opioid"
        ],
        "rxnorm_rxcui": "313782"
      },
      "11822317640": {
        "description_and_quantity": "Athenol 325 mg tablet",
        "med_medication_id": 436095,
        "search_terms": "Athenol 325 mg tablet|ACETAMINOPHEN ORAL|...|TYLENOL|...",
        "med_medication_description": "Athenol 325 mg tablet",
        "clinical_quantities": [
          {
            "erx_quantity": "1.0000000",
            "representative_ndc": "11822317640",
            "clinical_quantity_description": "tablet",
            "erx_ncpdp_script_quantity_qualifier_code": "C48542",
            "erx_ncpdp_script_quantity_qualifier_description": "Tablet"
          }
        ],
        "etc_path_id": [3645, 574, 578, 577],
        "etc_path_name": [
          "Analgesic, Anti-inflammatory or Antipyretic",
          "Analgesic, Anti-inflammatory or Antipyretic - Non-Opioid",
          "Analgesic or Antipyretic Non-Opioid and Combinations",
          "Analgesic or Antipyretic Non-Opioid"
        ],
        "rxnorm_rxcui": "313782"
      }
    }
    ```
**Codes that resolve vs. free text.** A `med_medication_id` returned above resolves cleanly when passed as an `fdb_code` to a command. Some FDB entries — for example the `THSC …`-prefixed results the FHIR `/Medication` search can return — are not in this catalog and will render with a **blank medication name** if used. To record a historical or non-catalog medication as free text instead, pass a `Coding` with `system=CodeSystems.UNSTRUCTURED` (its `display` is used as-is) — see [MedicationStatement](/sdk/commands/#medicationstatement).
###  Searching for allergens 
Plugin authors can search the allergen catalog — the same catalog behind the chart's allergen autocomplete — by full-text or by RxNorm code.
####  `GET /fdb/allergy` — full-text search 
**Used by:** [Allergy](/sdk/commands/#allergy) (`allergy` — the allergen `concept_id` \+ `concept_type`).
Full-text search of the allergen catalog. Query parameters:
Parameter | Type | Description  
---|---|---  
`dam_allergen_concept_id_description__fts` | string | Full-text search over the allergen concept description.  
    ```python
    from urllib.parse import urlencode
    from canvas_sdk.utils.http import ontologies_http
    response_json = ontologies_http.get_json(
        f"/fdb/allergy?{urlencode({'dam_allergen_concept_id_description__fts': 'chocolate'})}"
    ).json()
    ```
The response contains a `results` list of allergen concepts:
    ```json
    {
      "results": [
        {
          "dam_allergen_concept_id": 19561,
          "dam_allergen_concept_id_description": "chocolate",
          "concept_type": "ingredient"
        }
      ]
    }
    ```
####  `GET /fdb/allergen` — RxNorm lookup 
Look up allergen concepts mapped to an external code. Query parameters:
Parameter | Type | Description  
---|---|---  
`code` | string | The external code to match, in `{system}\|{code}` form — e.g. `rxnorm\|217013`.  
    ```python
    from urllib.parse import urlencode
    from canvas_sdk.utils.http import ontologies_http
    response_json = ontologies_http.get_json(
        f"/fdb/allergen?{urlencode({'code': 'rxnorm|217013'})}"
    ).json()
    ```
Each result carries the FDB vocabulary type and id:
    ```json
    {
      "results": [
        {
          "evd_fdb_vocabulary_type_identifier": 104,
          "imk_fdb_vocabulary_no_identifier": 19561,
          "imk_fdb_vocabulary_description": "chocolate"
        }
      ]
    }
    ```
####  `GET /fdb/allergy/` — look up by concept id 
Resolve a specific allergen concept by id — for example, to confirm a stored allergy. Query parameters:
Parameter | Type | Description  
---|---|---  
`dam_allergen_concept_id` | int | The FDB allergen concept id.  
`dam_allergen_concept_id_type` | int | The allergen category — `1` (allergy group), `2` (medication), or `6` (ingredient).  
    ```python
    from urllib.parse import urlencode
    from canvas_sdk.utils.http import ontologies_http
    response_json = ontologies_http.get_json(
        f"/fdb/allergy/?{urlencode({'dam_allergen_concept_id': 19561, 'dam_allergen_concept_id_type': 6})}"
    ).json()
    ```
Returns a `results` list containing the matching allergen concept (same fields as the full-text search above):
    ```json
    {
      "results": [
        {
          "dam_allergen_concept_id": 19561,
          "dam_allergen_concept_id_description": "chocolate",
          "concept_type": "ingredient"
        }
      ]
    }
    ```
####  Mapping results to `AllergyCommand`
To record one of these on a patient, pass the concept id and its type to [`AllergyCommand`](/sdk/commands/#allergy) as `concept_id` and `concept_type`. The catalog's allergen type maps to the command's category as follows:
Allergen type | `concept_type` (text search) | FDB vocabulary type (RxNorm search) | `AllergyCommand` category  
---|---|---|---  
Allergy group | `allergy group` | `110` | `1`  
Medication | `medication` | `1` | `2`  
Ingredient | `ingredient` | `104` | `6`  
For example, an allergy to chocolate is ingredient concept `19561` → category `6`.
###  Looking up clinical codes 
Beyond medications and allergens, the ontologies service resolves coded concepts that back several commands — ICD-10 conditions and CVX immunizations. (SNOMED concept searches are under Searching clinical concepts.)
####  `GET /icd/condition/` — ICD-10 conditions 
**Used by:** [Diagnose](/sdk/commands/#diagnose) (`icd10_code`), [Medical History](/sdk/commands/#medicalhistory) (`past_medical_history`), and the `diagnosis_codes` fields on [Refer](/sdk/commands/#refer), [Imaging Order](/sdk/commands/#imagingorder), and other diagnosis-code commands. _(Prescribe/Refill`icd10_codes` come from the patient's active conditions, not this search.)_
Search ICD-10 conditions by text, or resolve a specific code. This backs the condition/diagnosis autocompletes on commands such as [Diagnose](/sdk/commands/#diagnose), [Assess](/sdk/commands/#assess), and [Medical History](/sdk/commands/#medicalhistory). Query parameters:
Parameter | Type | Description  
---|---|---  
`search` | string | Full-text search over ICD-10 conditions.  
`icd10_code` | string | Resolve an exact ICD-10 code instead of searching. Dots optional (`E119` / `E11.9`).  
`date` | string | Optional. `YYYY-MM-DD`; resolves codes as of that date.  
`limit` | int | Optional. Maximum number of results.  
    ```python
    from urllib.parse import urlencode
    from canvas_sdk.utils.http import ontologies_http
    # full-text search
    response_json = ontologies_http.get_json(
        f"/icd/condition/?{urlencode({'search': 'type 2 diabetes'})}"
    ).json()
    # resolve a specific code
    response_json = ontologies_http.get_json(
        f"/icd/condition/?{urlencode({'icd10_code': 'E119'})}"
    ).json()
    ```
Returns a `results` list of matching conditions:
    ```json
    {
      "results": [
        {
          "icd10_code": "E11.9",
          "icd10_text": "Type 2 diabetes mellitus without complications",
          "snomed_concept_id": "44054006",
          "preferred_snomed_term": "Type 2 diabetes mellitus"
        }
      ]
    }
    ```
####  `GET /cpt/immunization/` — search immunizations 
**Used by:** [Immunization Statement](/sdk/commands/#immunizationstatement) (`cvx_code` / `cpt_code`).
Search vaccines by name or code. Query parameters:
Parameter | Type | Description  
---|---|---  
`name_or_code` | string | A vaccine code, or a text fragment of its name.  
`cvx_code` | string | Optional. Filter to a specific CVX code.  
    ```python
    from urllib.parse import urlencode
    from canvas_sdk.utils.http import ontologies_http
    response_json = ontologies_http.get_json(
        f"/cpt/immunization/?{urlencode({'name_or_code': 'influenza'})}"
    ).json()
    ```
The response contains a `results` list of matching vaccines:
    ```json
    {
      "results": [
        {
          "medium_name": "influenza, injectable, quadrivalent",
          "cpt_code": "90686",
          "cvx_code": "150",
          "cvx_description": "Influenza, injectable, quadrivalent"
        }
      ]
    }
    ```
###  Searching clinical concepts 
These endpoints back the concept autocompletes on several commands. Each returns a `results` list of matching concepts.
####  `GET /snomed/family-history/` — family-history conditions 
**Used by:** [Family History](/sdk/commands/#familyhistory) (`family_history`).
Search SNOMED family-history conditions.
Parameter | Type | Description  
---|---|---  
`search` | string | Full-text search over family-history conditions.  
`limit` | int | Optional. Maximum number of results.  
    ```python
    from urllib.parse import urlencode
    from canvas_sdk.utils.http import ontologies_http
    response_json = ontologies_http.get_json(
        f"/snomed/family-history/?{urlencode({'search': 'diabetes'})}"
    ).json()
    ```
The response contains a `results` list of SNOMED concepts:
    ```json
    {
      "results": [
        {
          "concept_id": "73211009",
          "term": "Diabetes mellitus"
        }
      ]
    }
    ```
####  `GET /snomed/family-relation/` — family relationships 
**Used by:** [Family History](/sdk/commands/#familyhistory) (`relative`).
Search SNOMED family relationships (mother, father, sibling, …).
Parameter | Type | Description  
---|---|---  
`term__icontains` | string | Case-insensitive substring match on the relationship term.  
    ```python
    from urllib.parse import urlencode
    from canvas_sdk.utils.http import ontologies_http
    response_json = ontologies_http.get_json(
        f"/snomed/family-relation/?{urlencode({'term__icontains': 'mother'})}"
    ).json()
    ```
The response contains a `results` list of SNOMED concepts:
    ```json
    {
      "results": [
        {
          "concept_id": "72705000",
          "term": "Mother"
        }
      ]
    }
    ```
####  `GET /snomed/instruction/` — instructions 
**Used by:** [Instruct](/sdk/commands/#instruct) (`coding`).
Search SNOMED instructions.
Parameter | Type | Description  
---|---|---  
`term__icontains` | string | Case-insensitive substring match on the instruction term.  
    ```python
    from urllib.parse import urlencode
    from canvas_sdk.utils.http import ontologies_http
    response_json = ontologies_http.get_json(
        f"/snomed/instruction/?{urlencode({'term__icontains': 'physical therapy'})}"
    ).json()
    ```
The response contains a `results` list of SNOMED concepts:
    ```json
    {
      "results": [
        {
          "concept_id": "229065009",
          "term": "Physical therapy"
        }
      ]
    }
    ```
####  `GET /snomed/procedures/` — surgical-history procedures 
**Used by:** [Surgical History](/sdk/commands/#surgicalhistory) (`past_surgical_history`).
Search SNOMED procedures.
Parameter | Type | Description  
---|---|---  
`search` | string | Full-text search over procedures.  
`limit` | int | Optional. Maximum number of results.  
    ```python
    from urllib.parse import urlencode
    from canvas_sdk.utils.http import ontologies_http
    response_json = ontologies_http.get_json(
        f"/snomed/procedures/?{urlencode({'search': 'appendectomy'})}"
    ).json()
    ```
The response contains a `results` list of SNOMED concepts:
    ```json
    {
      "results": [
        {
          "concept_id": "80146002",
          "term": "Appendectomy"
        }
      ]
    }
    ```
###  Screening for drug–allergy interactions 
**Used by:** the [Prescribe](/sdk/commands/#prescribe) safety screening; also callable directly from a plugin (it is not tied to a command field).
> **Info:** Canvas runs this screening automatically on staged medication commands in the UI and displays the results to the provider. If you are using the SDK to automate the charting of these commands, that interactive screening may not surface — you may want to run this check yourself. 
Canvas's drug–allergy screening — the same FDB-backed check the chart runs when a provider adds a medication — is available through `ontologies_http`.
`GET /fdb/medication-allergy/` takes a candidate medication and the patient's allergy list, both as JSON-encoded query parameters:
Parameter | Type | Description  
---|---|---  
`consideredMedication` | JSON string | The candidate drug's FDB codings as a flat list of `[code, "FDB"]` pairs, e.g. `[["217012", "FDB"]]`.  
`allergyList` | JSON string | The patient's allergies as `[code, category]` pairs, where `category` is the FDB allergen-concept type carried on the allergy record, e.g. `[["476", 1], ["1886", 6]]`.  
    ```python
    import json
    from urllib.parse import urlencode
    from canvas_sdk.commands.constants import CodeSystems
    from canvas_sdk.utils.http import ontologies_http
    from canvas_sdk.v1.data import Medication, Patient
    def fdb_codings(med: Medication) -> list[list[str]]:
        """The medication's FDB codings as [code, "FDB"] pairs."""
        return [
            [c.code, "FDB"]
            for c in med.codings.all()
            if c.code and c.system == CodeSystems.FDB
        ]
    def screen_medication_against_allergies(patient: Patient, considered_med: Medication) -> list[dict]:
        # Candidate medication: [[code, "FDB"], ...]
        considered_codings = fdb_codings(considered_med)
        # Patient's allergies: [[fdb_allergen_code, category], ...]
        allergy_ids = []
        for allergy in patient.allergy_intolerances.committed():
            fdb_coding = next(
                (c for c in allergy.codings.all() if c.code and c.system == CodeSystems.FDB),
                None,
            )
            if fdb_coding:
                allergy_ids.append([fdb_coding.code, allergy.category])
        if not considered_codings or not allergy_ids:
            return []  # nothing to screen → "all clear"
        params = urlencode(
            {
                "consideredMedication": json.dumps(considered_codings),
                "allergyList": json.dumps(allergy_ids),
            }
        )
        return ontologies_http.get_json(f"/fdb/medication-allergy/?{params}").json()
    ```
The response is a list of interaction objects — one per matched allergy, distinguishing a direct match from a cross-sensitive one (e.g. a penicillin allergy against a cephalosporin). An empty list `[]` means no interactions (all clear):
    ```json
    [
      {
        "drug": { "...": "full FDB medication payload" },
        "allergy_concept": {
          "dam_allergen_concept_id": 476,
          "dam_allergen_concept_id_type": 1,
          "dam_allergen_concept_id_description": "Penicillins"
        },
        "specific_ingredients": [],
        "cross_sensitive_ingredients": []
      }
    ]
    ```
###  Screening for drug–drug interactions 
**Used by:** the [Prescribe](/sdk/commands/#prescribe) safety screening; also callable directly from a plugin (it is not tied to a command field).
> **Info:** Canvas runs this screening automatically on staged medication commands in the UI and displays the results to the provider. If you are using the SDK to automate the charting of these commands, that interactive screening may not surface — you may want to run this check yourself. 
The sibling `GET /fdb/medication-list-interaction/` endpoint checks a single candidate medication against the patient's existing medication list. It checks the candidate against each existing med — it does **not** check the existing meds against each other. Both inputs are JSON-encoded query parameters:
Parameter | Type | Description  
---|---|---  
`consideredMedication` | JSON string | The candidate drug's FDB codings as a flat list of `[code, "FDB"]` pairs, e.g. `[["217012", "FDB"]]`.  
`medicationList` | JSON string | The patient's existing meds as a list of per-drug coding lists — **one inner list per drug** , e.g. `[[["155744", "FDB"]], [["261266", "FDB"]]]`.  
> **Group`medicationList` one inner list per drug.** If you flatten it to one entry per coding row, a multi-coding drug can appear to interact with itself and produce false positives.
    ```python
    import json
    from urllib.parse import urlencode
    from canvas_sdk.commands.constants import CodeSystems
    from canvas_sdk.utils.http import ontologies_http
    from canvas_sdk.v1.data import Medication, Patient
    def fdb_codings(med: Medication) -> list[list[str]]:
        """The medication's FDB codings as [code, "FDB"] pairs."""
        return [
            [c.code, "FDB"]
            for c in med.codings.all()
            if c.code and c.system == CodeSystems.FDB
        ]
    def screen_drug_drug(patient: Patient, considered_med: Medication) -> list[dict]:
        considered_medication = fdb_codings(considered_med)
        # One inner list per existing drug; exclude the candidate itself.
        medication_list = [
            fdb_codings(med)
            for med in Medication.objects.for_patient(patient).active()
            if med.id != considered_med.id
        ]
        medication_list = [codings for codings in medication_list if codings]
        if not considered_medication or not medication_list:
            return []
        params = urlencode(
            {
                "consideredMedication": json.dumps(considered_medication),
                "medicationList": json.dumps(medication_list),
            }
        )
        return ontologies_http.get_json(
            f"/fdb/medication-list-interaction/?{params}"
        ).json()
    ```
The response is a list of interaction objects, one per interacting pair:
    ```json
    [
      {
        "existing_medication": 155744,
        "considered_medication": 217012,
        "existing_medication_description": "metformin 500 mg tablet",
        "severity": 2,
        "severity_text": "Severe Interaction: Action is required to reduce the risk of severe adverse interaction.",
        "monograph_text": ["Drug A / Drug B", "Clinical Effects: ...", "..."]
      }
    ]
    ```
`severity` uses FDB's DDIM scale — **lower number = more clinically significant** (the chart uses the same scale):
`severity` | Meaning  
---|---  
`1` | Contraindicated drug combination  
`2` | Severe interaction  
`3` | Moderate interaction  
`9` | Undetermined severity / alternative therapy  
**Screening several new medications at once:** checking each new med only against the patient's _current_ list misses interactions _between_ the new meds. Accumulate — after screening each new med, add it to the list you screen the next one against:
    ```python
    from canvas_sdk.v1.data import Medication, Patient
    patient = Patient.objects.get(id="e5f6a7b8-9c0d-4e1f-8a2b-3c4d5e6f7a8b")
    new_meds = list(Medication.objects.none())  # the medications you're about to add
    existing = list(Medication.objects.for_patient(patient).active())
    for new_med in new_meds:
        # screen new_med against `existing`, then:
        existing.append(new_med)  # so the next new med is checked against it too
    ```
##  Making requests to the Pharmacy service 
Plugin authors can make requests to our Pharmacy service using the `PharmacyHttp` client. This client provides a simplified interface for searching pharmacies and retrieving pharmacy details.
    ```python
    from canvas_sdk.utils.http import pharmacy_http
    ```
Unlike the general Http client, PharmacyHttp only provides two specific methods for pharmacy operations. Direct HTTP methods (get, post, put, patch) are not available.
###  Searching for pharmacies 
Search for pharmacies using full-text search, specific field filters, or location-based ordering.
**Parameters** :
Name | Type | Required | Description  
---|---|---|---  
`search_term` | _string_ | `false` | Full-text search across name, address, city, state, zip, and NCPDP ID.  
`latitude` | _string_ | `false` | Latitude coordinate for location-based ordering.  
`longitude` | _string_ | `false` | Longitude coordinate for location-based ordering.  
`id` | _integer_ | `false` | Exact pharmacy ID match.  
`ncpdp_id` | _string_ | `false` | Exact NCPDP ID match.  
`organization_name` | _string_ | `false` | Case-insensitive contains match on organization name.  
`specialty_type` | _string_ | `false` | Case-insensitive contains match on specialty type (e.g. "Retail").  
`state` | _string_ | `false` | Case-insensitive exact match on state (e.g. "NY").  
`zip_code_prefix` | _string_ | `false` | One or more comma-separated zip code prefixes (e.g. "100,902").  
**Example** :
    ```python
    from canvas_sdk.utils.http import pharmacy_http
    # Full-text search by name
    results = pharmacy_http.search_pharmacies("CVS")
    # Search with location-based ordering
    results = pharmacy_http.search_pharmacies(
        "pharmacy",
        latitude="40.7128",
        longitude="-74.0060"
    )
    # Filter by state and specialty type
    results = pharmacy_http.search_pharmacies(state="NY", specialty_type="Retail")
    # Filter by zip code prefixes
    results = pharmacy_http.search_pharmacies(zip_code_prefix="100,902")
    # Look up by exact NCPDP ID
    results = pharmacy_http.search_pharmacies(ncpdp_id="1234567")
    # The results list contains pharmacy objects like:
    # [
    #   {
    #     "id": 123456,
    #     "distance_miles": 0.8,
    #     "ncpdp_id": "1234567",
    #     "store_number": "#1234",
    #     "organization_name": "CVS Pharmacy #1234",
    #     "address_line_1": "123 MAIN ST",
    #     "address_line_2": "",
    #     "city": "New York",
    #     "state": "NY",
    #     "zip_code": "10001",
    #     "phone_primary": "2125551234",
    #     "fax": "2125555678",
    #     "latitude": 40.7128,
    #     "longitude": -74.0060,
    #     "npi": "1234567890",
    #     "specialty_type": "Retail",
    #     ...
    #   },
    #   ...
    # ]
    ```
###  Looking up a pharmacy by NCPDP ID 
Retrieve detailed information about a specific pharmacy using its NCPDP (National Council for Prescription Drug Programs) identifier.
**Parameters** :
Name | Type | Required | Description  
---|---|---|---  
`ncpdp_id` | _string_ | `true` | The NCPDP identifier of the pharmacy.  
**Example** :
    ```python
    from canvas_sdk.utils.http import pharmacy_http
    # Look up a specific pharmacy
    pharmacy = pharmacy_http.get_pharmacy_by_ncpdp_id("1234567")
    # The response contains detailed pharmacy information:
    # {
    #   "id": 123456,
    #   "ncpdp_id": "1234567",
    #   "store_number": "#1234",
    #   "organization_name": "Example Pharmacy #1234",
    #   "address_line_1": "123 MAIN ST",
    #   "address_line_2": "",
    #   "city": "New York",
    #   "state": "NY",
    #   "zip_code": "10001",
    #   "country": "US",
    #   "standardized_address_line_1": "123 Main St",
    #   "standardized_city": "New York",
    #   "standardized_state": "NY",
    #   "standardized_zip_code": "100010000",
    #   "phone_primary": "2125551234",
    #   "fax": "2125555678",
    #   "email": "",
    #   "active_start_time": "2020-01-01T00:00:00Z",
    #   "active_end_time": "2099-12-31T23:59:59Z",
    #   "service_level": "New~Refill~Change~Cancel~ControlledSubstance",
    #   "npi": "1234567890",
    #   "specialty_type": "Retail",
    #   "dea_number": "",
    #   "organization_type": "Pharmacy",
    #   "organization_id": 1234567,
    #   "latitude": 40.7128,
    #   "longitude": -74.0060,
    #   ...
    # }
    ```
###  Response fields 
Both methods return pharmacy objects with the following key fields:
  - `ncpdp_id`: Unique NCPDP identifier for the pharmacy
  - `organization_name`: Name of the pharmacy
  - `address_line_1`, `address_line_2`, `city`, `state`, `zip_code`: Physical address
  - `phone_primary`: Primary phone number
  - `fax`: Fax number
  - `npi`: National Provider Identifier
  - `specialty_type`: Type of pharmacy (e.g., "Retail", "Mail Order")
  - `service_level`: Services available (e.g., "New~Refill~Change~Cancel~ControlledSubstance")
  - `latitude`, `longitude`: Geographic coordinates
  - `distance_miles`: Distance from search location (only present in search results when location is provided)
##  Making requests to the Science service 
Plugin authors can make requests to our Science service using the `science_http` wrapper. The Science service backs autocomplete behavior in the Canvas note UI for imaging order codes (via parse templates) and for imaging centers and other clinical contacts.
    ```python
    from canvas_sdk.utils.http import science_http
    ```
Like `ontologies_http` and `pharmacy_http`, `science_http` is a JSON-only client. You can call `get_json()` and access the response with `json()` and `status_code`. Direct `get`/`post`/`put`/`patch` methods are not available.
###  Searching for imaging codes 
Use this to populate the `image_code` field of [`ImagingOrderCommand`](/sdk/commands/#imagingorder). `GET /parse-templates/imaging-reports/` accepts these query parameters:
Parameter | Type | Description  
---|---|---  
`query` | string | Full-text search over imaging report templates.  
`format` | string | Response format; pass `json`.  
`limit` | int | Maximum number of results to return.  
    ```python
    from urllib.parse import urlencode
    from canvas_sdk.utils.http import science_http
    params = {"query": "chest x-ray", "format": "json", "limit": 10}
    response_json = science_http.get_json(f"/parse-templates/imaging-reports/?{urlencode(params)}").json()
    ```
The response contains a `results` list of imaging report templates:
    ```json
    {
      "results": [
        {
          "code": "71046",
          "name": "X-ray of chest, 2 views",
          "code_system": "CPT"
        }
      ]
    }
    ```
Use the returned `code` on the command:
    ```python
    from urllib.parse import urlencode
    from canvas_sdk.commands import ImagingOrderCommand
    from canvas_sdk.utils.http import science_http
    params = {"query": "chest x-ray", "format": "json", "limit": 10}
    response_json = science_http.get_json(f"/parse-templates/imaging-reports/?{urlencode(params)}").json()
    command = ImagingOrderCommand(
        note_uuid="8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47",
        image_code=response_json["results"][0]["code"],
        diagnosis_codes=["R05"],
    )
    ```
###  Searching for contacts and service providers 
`GET /contacts/` searches the external contact directory — referring providers, specialists, imaging centers, and other service providers. Use it to populate a `ServiceProvider` on commands such as [Refer](/sdk/commands/#refer) and [Imaging Order](/sdk/commands/#imagingorder), to add external care-team members, or to look up a fax number for outbound faxing. Imaging centers are just contacts with a radiology job title — filter for them with `job_title__icontains=radiology`.
Query parameters (all optional; combine as needed):
Parameter | Type | Description  
---|---|---  
`search` (or `query`) | string | Full-text search over first/last name, practice name, job title, business address, phone, and fax.  
`job_title__icontains` | string | Filter by job-title substring — pass `radiology` to limit results to imaging centers.  
`business_postal_code__in` | string | Comma-separated ZIP codes to bias toward local results.  
`first_name__icontains`, `last_name__icontains` | string | Filter by contact name.  
`practice_name__icontains` | string | Filter by practice name.  
`business_fax__icontains`, `business_fax` | string | Filter by fax number (substring, or exact match).  
`format` | string | Response format; pass `json`.  
    ```python
    from urllib.parse import urlencode
    from canvas_sdk.utils.http import science_http
    # General service-provider search (e.g. for a referral)
    params = {"search": "cardiology", "format": "json"}
    response_json = science_http.get_json(f"/contacts/?{urlencode(params)}").json()
    # Imaging centers: narrow to a radiology job title
    params = {
        "search": "advanced imaging",
        "job_title__icontains": "radiology",
        "format": "json",
        # Optional location filter:
        # "business_postal_code__in": "10001,10002",
    }
    response_json = science_http.get_json(f"/contacts/?{urlencode(params)}").json()
    ```
The response contains a `results` list of contact objects:
    ```json
    {
      "results": [
        {
          "firstName": "...",
          "lastName": "...",
          "practiceName": "Advanced Imaging Center",
          "specialty": "Radiology",
          "businessPhone": "2125551234",
          "businessFax": "2125555678",
          "businessAddress": "123 Main St, New York, NY 10001",
          "notes": "..."
        }
      ]
    }
    ```
Pass a selected contact as the `service_provider` on [Refer](/sdk/commands/#refer), [Imaging Order](/sdk/commands/#imagingorder), or other commands that accept a `ServiceProvider`; its `businessFax` and `businessAddress` also drive outbound faxing.
----- END PAGE https://docs.canvasmedical.com/sdk/utils/


