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, which reads a request body onto any command on this page, validates it, and emits the effects. The Writing Commands Over HTTP guide walks through building one.

Note content such as HPI, Review of Systems, Physical Exam, Assessment, and Plan is populated programmatically through this commands module. A common pattern for external integrations is to expose a SimpleAPI endpoint that uses the Create Note effect to create the note and then originates commands into it. See the charting API examples plugin for a worked example.

To write commands from an outside system over HTTP, use CommandAPI, which turns a command into an endpoint and validates each request against the command.

To go beyond the built-in commands, by adding fields, showing your own content, or adding commands of your own, see Customizing Commands Beyond the Built-ins.

“Commands” is an umbrella term for all the structured data within a patient’s note. Questionnaire is one specific command type. When a questionnaire is built, its use case in charting can be set to Physical Exam, Structured Assessment, Review of Systems, or Questionnaire; these all share the same underlying database structure but appear in the note as their own distinct command. The .originate() method works on all command types.

You can use the SDK to auto-populate questionnaire commands (and other commands) in response to an event rather than building many separate per-situation automations. Listen for an appropriate event — such as a note state change, another command being committed, or metadata being added — and originate the commands you need. You can also insert commands when an action button is clicked if a manual trigger is preferred.

Common Attributes #

Parameters #

All commands share the following init kwarg parameters:

NameTypeRequiredDescription
note_uuidstringtrue if creating a new commandThe id of the Note in which to insert the command.
command_uuidstringtrue if updating an existing commandThe id of the 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.

The Required to column (depending on the command’s terminal action) in each command’s parameter table below indicates whether a field must be set in order for the server to accept that terminal action. It does not mean the field is required to instantiate the class or to call .originate() or .edit(). You can create a command with every other field empty and the command appears in the note as fresh command, mirroring how a clinician picks a command from the menu in the UI and leaves it unfilled until they’re ready to finalize it.

The only field required to originate any command is note_uuid. Skip .commit() and the command persists in the note in its empty/staged state until the user (or a later plugin call) fills it in and commits it:

from canvas_sdk.commands import PrescribeCommand

# inside a handler's compute(), on an event whose context carries the note
p = PrescribeCommand()
p.note_uuid = self.event.context["note"]["uuid"]
return [p.originate()]

The same pattern works with any command class — PlanCommand(note_uuid=...), AssessCommand(note_uuid=...), MedicalHistoryCommand(note_uuid=...), etc.

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 lists the actions each command accepts — check it before relying on one. upsert_metadata works on any command, and set_custom_html works on any staged command, including custom commands.

To call these over HTTP rather than from a handler, see CommandAPI.

originate #

Returns an Effect that originates a new command in the note body.

Parameters:

NameTypeRequiredDefaultDescription
commitboolNoFalseWhen 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 for which commands support COMMIT.
line_numberintNo-1The 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.

Examples:

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:

from canvas_sdk.commands import AssessCommand

def compute():
    assess_command = AssessCommand(
        note_uuid='550e8400-e29b-41d4-a716-446655440000',
        icd10_code='E11.9'
    )

    return [assess_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:

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:

from canvas_sdk.commands import PlanCommand

def compute():
    existing_plan = PlanCommand(command_uuid='2b9d1f0a-4c3e-4b5a-9d8c-7e6f5a4b3c2d')

    return [existing_plan.delete()]

To delete the command that triggered your handler, instantiate the matching command class with command_uuid=self.target and return its delete() effect — for example, PlanCommand(command_uuid=self.target).delete() from a handler responding to PLAN_COMMAND__POST_ORIGINATE. If you instead want to mark a committed command as entered in error, use enter_in_error. Note that delete() only removes non-committed (staged) commands.

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 from a handler on the command’s validation event.

Example:

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:

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:

NameTypeRequiredDefaultDescription
practice_location_overridestr \| UUIDNoNonePrescribe only. The id of a 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:

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):

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. The command referenced by command_uuid must already be committed; returning this effect transitions it to entered-in-error status.

Example:

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:

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, OrderDme, and Refer command objects. Other command types do not support this operation.

Example:

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 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, which renders additional fields on the command whose values are saved as command metadata.

ParameterTypeDescription
keystringThe metadata key (max 256 characters).
valuestringThe metadata value.

Example:

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.

ParameterTypeDescription
custom_htmlstring or NoneThe HTML content to set on the command, or None to clear it.

Example:

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:

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:

from canvas_sdk.commands import AssessCommand

def compute():
    assess_command = AssessCommand(
        note_uuid='550e8400-e29b-41d4-a716-446655440000',
        icd10_code='E11.9'
    )

    return [assess_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 chaining is necessary because a handler returns all of its effects at once, so it cannot read back an id that Canvas assigns during origination.

from uuid import uuid4
from canvas_sdk.commands import AssessCommand

def compute():
    note_uuid = '550e8400-e29b-41d4-a716-446655440000'

    assess_command = AssessCommand(
        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.
    assess_command.command_uuid = str(uuid4())

    # Now we can both originate and commit in a single operation
    return [assess_command.originate(), assess_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.
ActionDescription
printGenerates a printable version of the command for documentation or external sharing.
audit_historyDisplays the complete audit trail for the command, showing all modifications, state changes, and user interactions over time.
carry_forwardPopulates the command with the last known data for this command type and patient, letting users quickly recreate a similar command from a previous entry.

How a printed note orders its commands depends on the note’s format. A note in the older format, or any note on an instance outside the revamped note beta program, groups its commands by section in SOAP order, and this sorting can’t be turned off. Within each section, commands print in a fixed order by command type, not in the order they were entered.

A note in the revamped format prints the sections it shows on screen, History, Exam, and Assessment & Plan, in the note’s SOAP or APSO order. Within each section, commands print in the order the provider arranged them in the note. Each Assess command prints with the commands linked to it indented below it. The Internal section isn’t printed, and free text prints under a Notes heading after the sections. Faxed notes and the patient’s entire-record print use the same layout.

To control the print layout yourself, build a plugin that adds an action button with a custom print template. The patient-visit-summary extension does this: staff choose and reorder the sections of a note, preview the result, and print it or save it as a PDF.

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:

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 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 documentation for the full API and examples.

Choosing Between Similar Commands #

Some commands write to the same part of the chart and differ only in intent. The tables below cover the cases where more than one command could plausibly apply.

Recording a condition #

Which command fits depends on whether the condition is active now, whether you are assessing it, and whether it is surgical.

CommandUse it when
Assess with icd10_codeThe condition is active and you are assessing it, whether it is new or already on the chart. It reuses the patient’s charted condition with that code, and records a new one only when the patient has none, so it never adds a duplicate. Use this for new work. Set show_in_problem_list=False for a diagnosis that belongs only to this visit.
MedicalHistoryYou are backfilling a condition the patient had in the past that is not active now.
AddConditionThe condition was diagnosed previously and is still active, and you are not assessing it.
SurgicalHistoryYou are recording a past surgical procedure.
DiagnoseStill available for a new active condition you are assessing for the first time, but planned for retirement in a future release. Use Assess with icd10_code instead.

Each of these writes a Condition when it records a new one. They differ in what they set on it, which is what decides where it shows up and which commands can act on it later:

Commandclinical_statussurgicalOpens an assessment
Assess with icd10_codeactivefalseYes
MedicalHistoryresolvedfalseNo
AddConditionactivefalseNo
SurgicalHistoryresolvedtrueNo
DiagnoseactivefalseYes

Surgical history is coded in SNOMED rather than ICD-10, and the surgical flag is what separates it from past medical history: Remove Past Medical History rejects a surgical entry, and AddCondition ignores one when checking whether a condition is already on the list.

Updating or removing a condition #

Four more commands act on a condition that is already on the chart.

CommandUse it when
AssessYou are recording an assessment against a condition on the chart, named by condition_id or icd10_code. Assessing a resolved condition makes it active again.
UpdateDiagnosisThe diagnosis was wrong or has been refined, and you want the new code to carry the original’s history.
Resolve ConditionAn active condition is no longer relevant to track. It must be committed, not entered in error, and not already resolved.
Remove Past Medical HistoryA past medical history entry does not belong on the chart at all. It must have no committed assessment against it. Resolve Condition will not take it, because the entry is already resolved.

The last two are easy to confuse. Resolve Condition closes a condition that was genuinely present and leaves it on the record as resolved. Remove Past Medical History withdraws an entry that should not have been recorded, taking it off the conditions list entirely.

Recording a medication #

Six commands touch a patient’s medication list. Three of them produce a prescription that can be sent to a pharmacy with send(), and three only change what the chart says.

CommandUse it whenProduces a prescription
PrescribeYou are prescribing a medication the patient is not already on.Yes
RefillYou are renewing a medication the patient is already on. The fdb_code must match one of that patient’s active medications.Yes
AdjustPrescriptionYou are replacing a prescribed medication with a different one, given as new_fdb_code. A different strength or formulation is a different FDB code, so it goes here too.Yes
ChangeMedicationYou are changing the sig on an active medication without issuing a new prescription.No
StopMedicationThe patient is stopping a medication, and you want the reason on the chart.No
MedicationStatementYou are recording a medication the patient reports taking, without prescribing it.No

Canceling a prescription that has already gone to a pharmacy is a Cancel Prescription command in the note. There is no SDK command class for it, so a plugin cannot originate one, though the resulting records are readable through the CancelPrescription data module.

Commands #

The sections below document each command class. See Common Attributes for the parameters and methods shared by all commands.

AddCondition #

Records a coded (ICD-10) condition on the patient’s Conditions list with clinical status active, in the note’s History section, without opening an assessment for it. Reach for it when the condition was diagnosed previously and is still active. See Recording a condition for how it compares to Assess and MedicalHistory.

Command-specific parameters:

NameTypeRequired to commitDescription
icd10_codestringtrueICD-10 code of the condition to record. Search with the ICD-10 condition endpoint.
backgroundstringfalseBackground information about the condition.
approximate_date_of_onsetdatefalseThe approximate date the condition began.
commentsstringfalseAdditional comments (max length: 1000 characters).

Example:

from canvas_sdk.commands import AddConditionCommand
from datetime import date

add_condition = AddConditionCommand(
    note_uuid='8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47',
    icd10_code='N183',
    background='Diagnosed at outside nephrology practice.',
    approximate_date_of_onset=date(2023, 4, 1),
    comments='Stable on last three panels.'
)

AdjustPrescription #

Replaces a prescribed medication with a different one, and can send the new prescription to a pharmacy. See Recording a medication for how it compares to the other medication commands.

Command-specific parameters:

NameTypeRequired to review / sendDescription
new_fdb_codestringtrueThe 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.

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:

NameTypeRequired to commitDescription
allergyAllergenfalseRepresents the allergen. See details in the Allergen type below. Search allergens with the ontologies allergen search.
severitySeverity enumfalseThe severity of the allergic reaction. Must be one of AllergyCommand.Severity.
narrativestringfalseA narrative or free-text description of the allergy (max length: 512 characters).
approximate_datedatetimefalseThe approximate date the allergy was identified.

Enums and Types:

Allergen

AttributeTypeDescription
concept_idintegerThe identifier for the allergen concept.
concept_typeAllergenType enumThe type of allergen. See AllergenType values below.

AllergenTypeValueDescription
ALLERGEN_GROUP1Represents a group of allergens.
MEDICATION2Represents a medication allergen.
INGREDIENT6Represents an ingredient allergen.

SeverityValueDescription
MILD"mild"Indicates a mild reaction.
MODERATE"moderate"Indicates a moderate reaction.
SEVERE"severe"Indicates a severe reaction.

Example:

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 #

Records an assessment of a patient’s condition. Name the condition in one of two ways:

  • icd10_code (recommended) assesses a condition by its ICD-10 code, whether or not it’s already on the chart, so there’s no need to look up a condition id first. If the patient already has a charted condition with that code, the assessment uses it, preferring an active condition over a resolved one. Committing the command makes a resolved condition active again. Codes match regardless of dots and case, so M54.50 and M5450 name the same condition. If the patient has no charted condition with that code, committing the command records a new one, on the problem list unless show_in_problem_list is False.
  • condition_id assesses a specific condition already on the patient’s chart.

Set one or the other, not both. You can originate an Assess with neither, for example to leave an empty command for the user to complete, but committing it needs a condition. See Recording a condition and Updating or removing a condition for how it compares to the other condition commands.

Entering a committed Assess in error also enters in error the condition its commit recorded, so withdrawing the assessment doesn’t leave a diagnosis on the chart that no one made. The condition stays on the chart while another committed assessment that isn’t entered in error still uses it. Conditions the Assess didn’t record, such as one already on the chart or one recorded by a Diagnose or AddCondition command, are never changed. You can always enter an Assess in error, even when other assessments use the condition it recorded.

Set show_in_problem_list to choose whether the condition belongs on the patient’s problem list or only to this visit. Committing with False records the condition as an encounter diagnosis, such as a sore throat that doesn’t need tracking. Committing with True moves an encounter diagnosis back onto the problem list. The choice is saved as the condition’s category, which the FHIR Condition resource returns as encounter-diagnosis or problem-list-item. An encounter diagnosis is still one assessed condition, not a copy, so Plan commands and billing in the note can use it. The chart’s Conditions list shows encounter diagnoses alongside problem list items. Clinicians can pick one in Update Diagnosis or Resolve Condition, or as a Prescribe indication, and an UpdateDiagnosis command can name it by code. A commit that changes the status sends CONDITION_UPDATED.

Entering in error an Assess that changed the status resets the condition’s status. The condition takes the status set by the most recently committed Assess that changed it and isn’t entered in error. If none remains, it goes back to the status it had before any Assess changed it.

Command-specific parameters:

NameTypeRequired to commitDescription
condition_idstringtrue, unless icd10_code is setThe id of the Condition being assessed. Must be a condition already recorded on that patient’s chart. Can’t be combined with icd10_code.
icd10_codestringtrue, unless condition_id is setICD-10 code of the condition being assessed. Search with the ICD-10 condition endpoint. Can’t be combined with condition_id.
approximate_date_of_onsetdatefalseThe approximate date the condition began. Committing the command saves it as the condition’s onset date, replacing any date already recorded. If you leave it unset, the condition’s onset date doesn’t change.
show_in_problem_listbooleanfalseWhether committing the command puts the condition on the problem list (True) or records it as an encounter diagnosis for this visit only (False). If you leave it unset or pass None, the condition keeps its current status, and a new condition goes on the problem list.
backgroundstringfalseBackground information about the diagnosis.
statusStatus enumfalseThe current status of the diagnosis. Must be one of AssessCommand.Status.
narrativestringfalseThe narrative for the current assessment (max 2048 characters; values exceeding the limit raise a validation error instead of being truncated).

StatusValueDescription
IMPROVED"improved"The condition has improved.
STABLE"stable"The condition is stable.
DETERIORATED"deteriorated"The condition has deteriorated.

Examples:

Assess a condition by ICD-10 code, whether or not the patient has it charted:

from datetime import date
from canvas_sdk.commands import AssessCommand

assess = AssessCommand(
    note_uuid='8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47',
    icd10_code='M54.50',
    approximate_date_of_onset=date(2012, 1, 1),
    background='lifted heavy box',
    narrative='unable to sleep lately'
)

Record a visit-only diagnosis as an encounter diagnosis:

from canvas_sdk.commands import AssessCommand

assess = AssessCommand(
    note_uuid='8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47',
    icd10_code='J02.9',
    show_in_problem_list=False,
    narrative='sore throat for two days'
)

Assess a specific condition already on the chart by its id:

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:

Passing both condition_id and icd10_code fails validation when you originate or edit the command, with the error Name the condition with either condition_id or icd10_code, not both. This applies even when one of them is None, because the empty value would clear the other. Leave out the argument you aren’t using.

Canvas checks icd10_code against the ICD-10 terminology when it applies the effect. A code that matches no ICD-10 condition fails with the error ICD-10 code <code> not found, and the command is neither created nor updated.

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 AssessCommands 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.

Committing the command checks condition_id again. If the condition was entered in error after it was picked, or isn’t on the patient’s chart, the commit fails with the error The condition picked in this Assess was entered in error or is not on this patient's chart. Pick the condition again.


ChangeMedication #

Changes the sig on an active medication without issuing a new prescription. See Recording a medication for how it compares to the other medication commands.

Command-specific parameters:

NameTypeRequired to commitDescription
medication_idstringtrueThe id of the Medication being changed. Must be an active medication on that patient’s chart.
sigstringfalseAdministration details of the medication.
assessment_idstringfalseThe id (a UUID) of an Assessment made in the same note as this command. A cross-note reference or an unknown id is rejected on originate and edit. The link clears automatically if that Assessment is deleted or entered in error, or if a move puts the two in different notes.

Example:

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 data model.

Command-specific parameters:

NameTypeRequired to commitDescription
sectionChartSectionReviewCommand.Sections enumtrueThe chart section being reviewed. Required when instantiating the command. Must be one of ChartSectionReviewCommand.Sections.

Example:

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 #

MemberValueChart section
CONDITIONSconditionsConditions
SURGICAL_HISTORYsurgical_historySurgical History
MEDICATIONSmedicationsMedications
FAMILY_HISTORYfamily_historiesFamily Histories
ALLERGIESallergiesAllergies
IMMUNIZATIONSimmunizationsImmunizations

CloseGoal #

Command-specific parameters:

NameTypeRequired to commitDescription
goal_idinttrueThe dbid of the Goal being closed. Must be a goal on that patient’s chart.
achievement_statusAchievementStatus enumfalseThe final achievement status of the goal. Must be one of GoalCommand.AchievementStatus.
progressstringfalseA narrative about the patient’s progress toward the goal.
assessment_idstringfalseThe id (a UUID) of an Assessment made in the same note as this command. A cross-note reference or an unknown id is rejected on originate and edit. The link clears automatically if that Assessment is deleted or entered in error, or if a move puts the two in different notes.

Example:

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 #

Records an active condition and assesses it for the first time. See Recording a condition for how it compares to Assess, MedicalHistory and AddCondition.

Command-specific parameters:

NameTypeRequired to commitDescription
icd10_codestringtrueICD-10 code of the condition being diagnosed. Search with the ICD-10 condition endpoint.
backgroundstringfalseBackground information about the diagnosis.
approximate_date_of_onsetdatetimefalseThe approximate date the condition began.
today_assessmentstringfalseThe narrative for the initial assessment of the condition (max length: 2048 characters).

Example:

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:

NameTypeRequired to commitDescription
family_historystring or CodingtrueA description of the family history being documented. Search with the family-history endpoint.
relativestringfalseA description of the relative (e.g., mother, uncle). Search with the family-relation endpoint.
notestringfalseAdditional 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:

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:

NameTypeRequired to commitDescription
structuredbooleanfalseWhether the RFV is structured or not. Defaults to False.
requested_datedatefalseThe desired follow up date.
note_type_idUUID (str)falseThe desired type of appointment. See NoteType.
codingCoding or UUID (str)true if structured=TrueThe 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 records
commentstringfalseAdditional commentary on the RFV.
assessment_idstringfalseThe id (a UUID) of an Assessment made in the same note as this command. A cross-note reference or an unknown id is rejected on originate and edit. The link clears automatically if that Assessment is deleted or entered in error, or if a move puts the two in different notes.

Example:

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:

NameTypeRequired to commitDescription
goal_statementstringtrueDescription of the goal.
start_datedatetimefalseThe date the goal begins.
due_datedatetimefalseThe date the goal is due.
achievement_statusAchievementStatus enumfalseThe current achievement status of the goal.
priorityPriority enumfalseThe priority of the goal.
progressstringfalseA narrative about the patient’s progress toward the goal.
assessment_idstringfalseThe id (a UUID) of an Assessment made in the same note as this command. A cross-note reference or an unknown id is rejected on originate and edit. The link clears automatically if that Assessment is deleted or entered in error, or if a move puts the two in different notes.

AchievementStatusValueDescription
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.

PriorityValueDescription
HIGH"high-priority"High priority.
MEDIUM"medium-priority"Medium priority.
LOW"low-priority"Low priority.

Example:

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:

NameTypeRequired to commitDescription
narrativestringtrueThe narrative of the patient’s history of present illness.

Example:

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:

NameTypeRequired to delegate / signDescription
image_codestringtrueCode identifier of the imaging order. Search with the imaging-codes endpoint.
diagnosis_codeslist[string]trueICD-10 Diagnosis codes justifying the imaging order. Search with the ICD-10 condition endpoint.
priorityPriority enumfalsePriority of the imaging order. Must be one of ImagingOrderCommand.Priority.
additional_detailsstringfalseAdditional details or instructions related to the imaging order (max length: 1024 characters).
service_providerServiceProvidertrueService provider of the imaging order. Search with the contacts endpoint.
commentstringfalseAdditional comments (max length: 1024 characters).
ordering_provider_keystringtrueThe Staff id of the provider ordering the imaging.
linked_items_urnslist[string]falseList of URNs for items linked to the imaging order command.
assessment_idstringfalseThe id (a UUID) of an Assessment made in the same note as this command. A cross-note reference or an unknown id is rejected on originate and edit. The link clears automatically if that Assessment is deleted or entered in error, or if a move puts the two in different notes. Linking also adds the assessed condition’s ICD-10 codes to diagnosis_codes unless they’re already listed.

Command-specific actions:

Action NameAvailable WhenDescription
delegate_actioncommand is stagedDelegates the order by creating a task.
sign_actioncommand is stagedSigns the order, transitioning it from staged to committed state.
print_specialistcommand is committedPrints the order using a specialist-focused template.
print_patientcommand is committedPrints the order using a patient-friendly template.
faxcommand is committedTransmits the order electronically via fax.

Enums and Types:

Priority

PriorityValueDescription
ROUTINE"Routine"A routine order.
URGENT"Urgent"An urgent order.
STAT"STAT"A STAT (immediate) order.

Example:

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:

NameTypeRequired to commitDescription
report_idslist[string]trueList of ImagingReport IDs to review. Must be reports on that patient’s chart.
message_to_patientstringfalseMessage to communicate findings to the patient.
communication_methodReportReviewCommunicationMethod enumfalseMethod for patient communication. Must be one of ReportReviewCommunicationMethod.
linked_items_urnslist[string]falseList of URNs for items linked to the review.
commentstringfalseInternal comment about the review.

Example:

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:

NameTypeRequired to commitDescription
cpt_codestring or Codingfalse*The CPT code for the immunization procedure. Used with CVX code to search against ontologies server for validation. Search with the immunization endpoint.
cvx_codestring or Codingfalse*The CVX code for the vaccine administered. Used with CPT code to search against ontologies server for validation. Search with the immunization endpoint.
unstructuredCodingfalse*Free-text immunization description.
approximate_datedatefalseThe approximate date when the immunization was administered.
commentsstringfalseAdditional 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:

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:

NameTypeRequired to commitDescription
vaccine_idUUIDtrueThe id of a Vaccine in this instance’s catalog. Must be active.
lot_idUUIDfalse*The id of a VaccineLot with doses on hand.
lot_numberstringfalse*A lot number this instance does not stock, recorded as free text (max 20 characters).
manufacturerstringfalseThe vaccine’s manufacturer (max 100 characters).
expiration_datedatefalseThe lot’s expiration date.
sigstringfalseDirections, as free text - for example "0.5 mL IM, left deltoid" (max 75 characters).
consent_givenbooleantrueWhether the patient consented after reviewing the Vaccine Information Statement. Must be true to commit.
given_by_idstringtrueThe id of the Staff member who administered the vaccine. Must be active.
assessment_idstringfalseThe id (a UUID) of an Assessment made in the same note as this command. A cross-note reference or an unknown id is rejected on originate and edit. The link clears automatically if that Assessment is deleted or entered in error, or if a move puts the two in different notes.

*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 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:

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:

NameTypeRequired to commitDescription
codingCodingtrueThe SNOMED code or UNSTRUCTURED code that represents the instruction. Search SNOMED with the instruction endpoint.
commentstringfalseAdditional comments related to the instruction.
assessment_idstringfalseThe id (a UUID) of an Assessment made in the same note as this command. A cross-note reference or an unknown id is rejected on originate and edit. The link clears automatically if that Assessment is deleted or entered in error, or if a move puts the two in different notes.

The Educational Material command only supports a fixed set of selectable values and is not available in this commands module. For free-text patient education, use InstructCommand instead — its UNSTRUCTURED coding accepts arbitrary instruction text.

Example:

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.

There is no dedicated endpoint for ordering labs or medications. Labs and prescriptions are ordered by creating LabOrderCommand and Prescribe commands in a note via the SDK, typically in response to an event. 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:

NameTypeRequired to sendDescription
lab_partnerstringtrueThe lab partner processing the order. Accepts either the lab partner’s name or its unique identifier (ID).
tests_order_codeslist[string]trueA list of codes or IDs for the tests being ordered. The system verifies that each provided value corresponds to an available test for the specified lab partner.
ordering_provider_keystringfalseThe Staff id of the provider ordering the tests.
diagnosis_codeslist[string]falseICD-10 Diagnosis codes justifying the lab order. Search with the ICD-10 condition endpoint.
fasting_requiredbooleanfalseIndicates if fasting is required for the tests.
commentstringfalseAdditional comments related to the lab order (max length: 128 characters).
assessment_idstringfalseThe id (a UUID) of an Assessment made in the same note as this command. A cross-note reference or an unknown id is rejected on originate and edit. The link clears automatically if that Assessment is deleted or entered in error, or if a move puts the two in different notes. Linking also adds the assessed condition’s ICD-10 codes to diagnosis_codes unless they’re already listed.

Command-specific actions:

Action NameAvailable WhenDescription
sign_send_actioncommand is stagedSigns and immediately sends the order electronically to the lab partner.
send_actioncommand is stagedSends the order electronically to the chosen lab partner.
sign_actioncommand is stagedSigns the order, transitioning it from staged to committed state.
print_requisition_formcommand is committedPrints the order using a requisition-focused template for lab submission.
print_specimen_labelcommand is committedPrints the template using a specimen-focused template.
fax_requisition_formcommand is committedTransmits the order electronically via fax.

ABN Workflow Actions

When the ABN (Advance Beneficiary Notice) workflow is enabled, additional actions become available:

Action NameAvailable WhenDescription
send_abn_signedcommand is stagedSends the order electronically after ABN requirements are met.
make_changescommand is stagedAllows 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:

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:

NameTypeRequired to commitDescription
report_idslist[string]trueList of LabReport IDs to review. Must be reports on that patient’s chart.
message_to_patientstringfalseMessage to communicate findings to the patient.
communication_methodReportReviewCommunicationMethod enumfalseMethod for patient communication. Must be one of ReportReviewCommunicationMethod.
linked_items_urnslist[string]falseList of URNs for items linked to the review.
commentstringfalseInternal comment about the review.

Example:

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 #

Backfills a condition the patient had in the past that is not active now. See Recording a condition for how it compares to Assess and AddCondition.

Command-specific parameters:

NameTypeRequired to commitDescription
past_medical_historystringtrueAn 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.
approximate_start_datedatefalseApproximate start date of the condition.
approximate_end_datedatefalseApproximate end date of the condition.
show_on_condition_listbooleanfalseWhether the condition should appear on the condition list.
commentsstringfalseAdditional 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:

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 #

Records a medication the patient reports taking, without prescribing it. See Recording a medication for how it compares to the other medication commands.

Command-specific parameters:

NameTypeRequired to commitDescription
fdb_codestring or CodingtrueThe FDB code of the medication
sigstringfalseAdministration 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

Converting an RxNorm code to an FDB code: MedicationStatement requires an FDB code; it cannot be originated directly from an RxNorm RXCUI. If you have an RxNorm code, use the ontologies service grouped-medication lookup, which returns a med_medication_id — that value is the FDB code:

from urllib.parse import urlencode

from canvas_sdk.utils.http import ontologies_http

# search for a specific RxNorm RXCUI
response_json = ontologies_http.get_json(f"/fdb/grouped-medication/?{urlencode({'rxnorm_rxcui': 313782})}").json()

Example:

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'
    )
)

OrderDme #

Orders durable medical equipment (DME), such as a wheelchair, catheter, or shower chair, from a note and sends the order to a supplier. Order a product from your DME formulary with dme_product_id, or describe an off-formulary item with free_text_item.

Admins manage the formulary in the Django Admin UI under DME Products. In a plugin, look up a product and its ID with the DmeProduct data model, as the example below does.

Command-specific parameters:

NameTypeRequired to signDescription
dme_product_idstringtrue, unless free_text_item is setThe id of an active DmeProduct in your DME formulary. Can’t be combined with free_text_item.
free_text_itemstringtrue, unless dme_product_id is setDescription of an off-formulary item (max length: 255 characters). Can’t be combined with dme_product_id.
diagnosis_codeslist[string]trueICD-10 Diagnosis codes justifying the order. Search with the ICD-10 condition endpoint.
quantityintegertrueNumber of units ordered. Must be at least 1.
length_of_needstringfalseHow long the patient needs the equipment, such as "6 months" or "Lifetime" (max length: 255 characters). Canvas prints this text on the order as written.
service_providerServiceProviderfalseThe supplier the order is sent to. Search with the contacts endpoint.
ordering_provider_keystringtrueThe Staff id of the provider ordering the equipment. The staff member must be active and able to prescribe.
assessment_idstringfalseThe id (a UUID) of an Assessment made in the same note as this command. A cross-note reference or an unknown id is rejected on originate and edit. The link clears automatically if that Assessment is deleted or entered in error, or if a move puts the two in different notes. Linking also adds the assessed condition’s ICD-10 codes to diagnosis_codes unless they’re already listed.

Setting both dme_product_id and free_text_item, a quantity below 1, or a length_of_need longer than 255 characters raises a validation error when you create the command object.

Canvas leaves a field empty when its value doesn’t resolve. A dme_product_id resolves only to an active formulary product, and an ordering_provider_key only to an active staff member. Diagnosis codes that don’t match an ICD-10 condition are dropped. The command can’t be signed until every required field has a value. Signing also fails when the ordering provider isn’t an active prescriber, because the printed order names them as the signer.

Signing saves the item’s formulary name and code and the indications with the order, along with the patient’s latest height and weight. Later changes to the formulary or the patient’s vitals don’t change the printed order.

Command-specific actions:

Action NameAvailable WhenDescription
sign_actioncommand is stagedSigns the order, transitioning it from staged to committed state.
print_ordercommand is committedPrints the order as a requisition. Requires printing access.
faxcommand is committedFaxes the order, prefilling the supplier’s fax number when one is on file. Requires printing access.

To filter or annotate the products, indications, suppliers, and ordering providers a user sees while filling in the command, handle the PRE_SEARCH and POST_SEARCH events for the ITEM, INDICATIONS, SEND_TO, and ORDERING_PROVIDER fields, such as ORDER_DME__ITEM__POST_SEARCH.

Suppliers you create with the ServiceProvider effect aren’t searched in Send to automatically. To offer them there, see Offering your own providers alongside the directory.

Example:

from canvas_sdk.commands import OrderDmeCommand
from canvas_sdk.commands.constants import ServiceProvider
from canvas_sdk.v1.data import DmeProduct

supplier = ServiceProvider(
    first_name="Acme",
    last_name="Medical Supply",
    practice_name="Acme Medical Supply",
    specialty="Durable Medical Equipment",
    business_fax="5555550100",
)

# A product from the DME formulary, looked up by its HCPCS code.
# get() raises an error unless exactly one active product has this code.
wheelchair = DmeProduct.objects.get(code="K0001", active=True)

formulary_order = OrderDmeCommand(
    note_uuid="8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47",
    dme_product_id=str(wheelchair.id),
    diagnosis_codes=["N186"],
    quantity=1,
    length_of_need="Lifetime",
    service_provider=supplier,
    ordering_provider_key="b8a7c6d5-4e3f-4a2b-9c1d-0e8f7a6b5c4d",
)

# An off-formulary item
free_text_order = OrderDmeCommand(
    note_uuid="8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47",
    free_text_item="Bariatric shower chair",
    diagnosis_codes=["E6601"],
    quantity=1,
    length_of_need="12 months",
    service_provider=supplier,
    ordering_provider_key="b8a7c6d5-4e3f-4a2b-9c1d-0e8f7a6b5c4d",
)

SurgicalHistory #

Records a past surgical procedure, coded in SNOMED. See Recording a condition for how it compares to the other condition commands.

Command-specific parameters:

NameTypeRequired to commitDescription
past_surgical_historystring or CodingtrueA description of the past surgical procedure. Search with the procedures endpoint.
approximate_datedatefalseApproximate date of the surgery.
commentstringfalseAdditional 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:

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:

NameTypeRequired to commitDescription
cpt_codestring or CodingtrueThe CPT code of the procedure or action performed. Look it up in the Charge Description Master.
notesstringfalseAdditional notes related to the performed procedure.
assessment_idstringfalseThe id (a UUID) of an Assessment made in the same note as this command. A cross-note reference or an unknown id is rejected on originate and edit. The link clears automatically if that Assessment is deleted or entered in error, or if a move puts the two in different notes.

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:

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:

NameTypeRequired to commitDescription
narrativestringtrueThe narrative of the patient’s plan.
assessment_idstringfalseThe id (a UUID) of an Assessment made in the same note as this command. A cross-note reference or an unknown id is rejected on originate and edit. The link clears automatically if that Assessment is deleted or entered in error, or if a move puts the two in different notes.

Example:

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.
  • Each test_values entry’s label matches one of the template’s field labels (case-insensitive).

Command-specific parameters:

NameTypeRequired to commitDescription
templateUUID | stringtrueThe UUID of the active POC LabReportTemplate. Accepts UUID instances or UUID-formatted strings.
indicationslist[string]falseICD-10 diagnosis codes justifying the test. Search with the ICD-10 condition endpoint.
test_valueslist[TestValue]falseThe measured values, each tagged with its template-field label. See TestValue below.
remarksstring (≤512)falseFree-text comments from the clinician.

Enums and Types:

TestValue

A dataclass representing a single measured value within a POC lab test result.

AttributeTypeDescription
labelstringThe template field’s label (must match a field on the template).
valuestringThe 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 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. Unknown labels cause a validation error.

The valid labels are the label of each LabReportTemplateField on the template’s fields relation:

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:

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 #

Writes a new prescription for a medication the patient is not already on, and can send it to a pharmacy. See Recording a medication for how it compares to the other medication commands.

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 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:

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. 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.

Signing is not supported programmatically. A Prescribe command cannot be committed (signed) via the SDK. Surescripts requires certain elements to be present in the UI when prescribing in order to remain certified, so the prescriber must physically sign the prescription in the UI. The sign and send actions were separated into distinct steps so that the send step can be performed programmatically, but only after the prescription has been manually signed in the UI. The send all prescriptions example plugin demonstrates the supported programmatic-send flow.

Command-specific parameters:

NameTypeRequired to review / sendDescription
fdb_codestringfalse*The FDB code of the medication.
compound_medication_idstringfalse*The id of an existing CompoundMedication to prescribe.
compound_medication_dataCompoundMedicationDatafalse*Data for creating a new compound medication inline.
icd10_codeslist[string]falseList of ICD-10 codes (maximum 2) associated with the prescription. Must be active Conditions on the patient’s chart, including encounter diagnoses kept off the problem list.
sigstringtrueAdministration instructions/details of the medication. Up to 1000 characters — see Limits.
days_supplyintegerfalseNumber of days the prescription is intended to cover.
quantity_to_dispenseDecimal | float | integertrueThe amount of medication to dispense. Must be greater than zero — see Limits.
type_to_dispenseClinicalQuantitytrue**Information about the form or unit of the medication to dispense. Get the available quantities from the medication search’s clinical_quantities.
refillsintegertrueNumber of refills allowed for the prescription. From 0 to 99 — see Limits.
substitutionsSubstitutions enumtrueSpecifies whether substitutions (e.g., generic drugs) are allowed.
pharmacystringfalseThe NCPDP ID of the pharmacy where the prescription should be sent. Look it up via the pharmacy search.
prescriber_idstringtrueThe Staff id of the prescriber.
supervising_provider_idstringfalseThe Staff id of the supervising provider of the prescriber.
note_to_pharmaciststringfalseAdditional notes or instructions for the pharmacist. Up to 210 characters — see Limits.
assessment_idstringfalseThe id (a UUID) of an Assessment made in the same note as this command. A cross-note reference or an unknown id is rejected on originate and edit. The link clears automatically if that Assessment is deleted or entered in error, or if a move puts the two in different notes. Linking also adds the assessed condition to icd10_codes unless it’s already listed. A prescription holds at most two codes, so a link that would add a third is rejected.

*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.

Compound medications: Compound medications have no FDB code, so they are referenced with compound_medication_id or compound_medication_data rather than fdb_code. Because a compound medication has no FDB code, the quantity-to-dispense format is not preselected — the same as the UI behavior — and must be chosen manually in the UI after the command is inserted. Do not supply type_to_dispense for compound medications.

Command-specific actions:

Action NameAvailable WhenDescription
sign_send_actioncommand is in reviewSigns and immediately sends the prescription electronically.
sign_actioncommand is in reviewSigns the prescription, transitioning it from staged to committed state.
print_actioncommand is in reviewPrints and commits the command.
make_changescommand is in reviewAllow users to revert the command to staged state and make changes.
send_actioncommand is committedSends the prescription electronically.

Enums and Types

SubstitutionsValueDescription
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 NameTypeDescriptionRequired
formulationstringThe compound medication formulation (max 105 characters)true
potency_unit_codePotencyUnit valueThe unit of measurement for the medication.true
controlled_substanceControlledSubstanceSchedule valueThe controlled substance schedule (N for none).true
controlled_substance_ndcstringNDC for controlled substances (dashes removed)false*
activeboolWhether the compound medication is active (default: true)false

*Required when controlled_substance is not “N” (None)

Examples

Option 1: Standard Prescription (FDB Code)

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

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

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.

FieldLimit
sig1000 characters
note_to_pharmacist210 characters
refills0 to 99
quantity_to_dispensegreater 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:

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:

NameTypeRequired to commitDescription
questionnaire_idstringtrueThe id of the Questionnaire being answered by the patient. The questionnaire’s use case in charting must be Physical Exam, and it must be set to originate in charting.
answerslist of AnswerfalseThe 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 dbid (an integer, accepted as int or str):

Methods:

MethodParametersReturnsDescription
is_question_enabledquestion_id: str or intboolCheck if a specific question is enabled (not skipped).
set_question_enabledquestion_id: str or int, enabled: boolNoneEnable or disable a specific question.

Properties:

PropertyTypeDescription
question_togglesdictAll 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:

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:

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.

Note on the scoring narrative: Setting a result value on the command instance has no effect — it is ignored by the command methods (originate, edit, commit). The questionnaire scoring narrative is instead set through the Create a Questionnaire Result effect. Once set, the narrative can be read from the command’s data attribute, displays in the UI command, and (if the questionnaire is configured to show in the Social Determinants section) displays there too.

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:

NameTypeRequiredDescription
question_idintegertrueThe Question dbid.
responsestring, integer, or list of SelectiontrueText for a text question, a number for an integer question, a 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.

Selection fields:

NameTypeRequiredDescription
option_idintegertrueThe ResponseOption dbid to tick.
commentstringfalseWhat this selection is qualified with.
selectedbooleanfalseDefaults 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:

NameTypeRequired to commitDescription
questionnaire_idstringtrueThe id of the Questionnaire being answered by the patient.
answerslist of AnswerfalseThe responses to record, one per question. Defaults to an empty list.

Example — instantiating an empty questionnaire:

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:

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),
          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"),
                  ],
              ),
          ],
      )

      # The responses are included when the command is originated.
      return [command.originate()]

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:

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)

      # 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")

      # The responses are included when the command is originated.
      return [command.originate()]

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: responses recorded with answers or add_response() are included when the command is originated, so [command.originate()] inserts the questionnaire already filled in, and command.originate(commit=True) also commits it. To change the responses of a questionnaire command that is already in the note, set them on a command with the same command_uuid and return its edit().

ReasonForVisit #

Command-specific parameters:

NameTypeRequiredDescription
structuredbooleanfalseWhether the RFV is structured or not. Defaults to False.
codingCoding or UUID (str)true if structured=TrueThe 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 records
commentstringfalseAdditional commentary on the RFV.

Example:

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:

NameTypeRequired to delegate / signDescription
service_providerServiceProvidertrueThe service provider associated with the referral command. Search with the contacts endpoint.
diagnosis_codeslist[string]trueA list of relevant ICD-10 Diagnosis. Search with the ICD-10 condition endpoint.
clinical_questionClinicalQuestion enumtrueThe clinical question prompting the referral. Must be one of ReferCommand.ClinicalQuestion
priorityPriority enumfalsePriority of the imaging order. Must be one of ReferCommand.Priority.
notes_to_specialiststringtrueNotes or additional information directed to the specialist.
include_visit_notebooleanfalseFlag indicating whether the visit note should be included in the referral.
commentstringfalseAn optional comment providing further details about the referral.
linked_items_urnslist[string]falseList of URNs for items linked to the referral command.
assessment_idstringfalseThe id (a UUID) of an Assessment made in the same note as this command. A cross-note reference or an unknown id is rejected on originate and edit. The link clears automatically if that Assessment is deleted or entered in error, or if a move puts the two in different notes. Linking also adds the assessed condition’s ICD-10 codes to diagnosis_codes unless they’re already listed.

Command-specific actions:

Action NameAvailable WhenDescription
delegate_actioncommand is stagedDelegates the order by creating a task.
sign_actioncommand is stagedSigns the order, transitioning it from staged to committed state.
print_specialistcommand is committedPrints the order using a specialist-focused template.
print_patientcommand is committedPrints the order using a patient-friendly template.
faxcommand is committedTransmits the order electronically via fax.

Enums and Types:

Priority

PriorityValueDescription
ROUTINE"Routine"A routine referral.
URGENT"Urgent"An urgent referral.
STAT"STAT"A STAT (immediate) referral.

ClinicalQuestion

Clinical QuestionValueDescription
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:

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().

Read the committed reference back with the Reference data model.

Command-specific parameters:

NameTypeRequired to commitDescription
diagnostic_view_idUUID or stringtrueThe id of the 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:

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:

NameTypeRequired to commitDescription
report_idslist[string]trueList of ReferralReport IDs to review. Must be reports on that patient’s chart.
message_to_patientstringfalseMessage to communicate findings to the patient.
communication_methodReportReviewCommunicationMethod enumfalseMethod for patient communication. Must be one of ReportReviewCommunicationMethod.
linked_items_urnslist[string]falseList of URNs for items linked to the review.
commentstringfalseInternal comment about the review.

Example:

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 #

Renews a prescription for a medication the patient is already on, and can send it to a pharmacy. See Recording a medication for how it compares to the other medication commands.

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:

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:

NameTypeRequired to commitDescription
allergy_idstringtrueThe id of the AllergyIntolerance to remove. Must be an allergy already recorded on that patient’s chart.
narrativestringfalseAdditional context or narrative for the removal (max length: 512 characters).

Example:

from canvas_sdk.commands import RemoveAllergyCommand

RemoveAllergyCommand(
    allergy_id="e5f6a7b8-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
    narrative="Allergy no longer applies after reassessment."
)

Remove Past Medical History #

Command-specific parameters:

NameTypeRequired to commitDescription
condition_idstringtrueThe id of the Condition being removed from the patient’s past medical history. Must be a committed, resolved, non-surgical condition already recorded on that patient’s chart, with no committed assessment against it. An assessment that was entered in error, or is not committed yet, does not block removal.
rationalestringfalseAdditional context or narrative for the removal (max length: 512 characters).

Committing this command enters the target condition in error, removing it from the patient’s conditions list. The Past Medical History command that originally recorded the entry stays committed and visible in the note. Entering this command in error reverses the removal, returning the entry to the patient’s chart. Each removal is recorded as a RemovePastMedicalHistoryEvent, which you can query from the data module.

Example:

from canvas_sdk.commands import RemovePastMedicalHistoryCommand
from canvas_sdk.v1.data import Condition

patient_id = "1eed3ea2a8d546a1b681a2a45de1d790"

# Leave out entries with a committed assessment, which this command refuses.
past_medical_history_entry = (
    Condition.objects.for_patient(patient_id)
    .committed()
    .filter(clinical_status="resolved", surgical=False)
    .exclude(assessments__committer__isnull=False, assessments__entered_in_error__isnull=True)
    .first()
)

RemovePastMedicalHistoryCommand(
    condition_id=past_medical_history_entry.id,
    rationale="Imported in error from the HIE feed.",
    note_uuid="8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47",
)

Resolve Condition #

Closes an active condition that is no longer relevant to track. See Updating or removing a condition for how it compares to the other condition commands.

Command-specific parameters:

NameTypeRequired to commitDescription
condition_idstringtrueThe id of the 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_listbooleanfalseDetermines whether the condition remains visible in patient chart summary.
rationalestringfalseAdditional context.
from canvas_sdk.commands.commands.resolve_condition import ResolveConditionCommand
from canvas_sdk.v1.data import Condition

patient_id = "1eed3ea2a8d546a1b681a2a45de1d790"

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:

NameTypeRequired to commitDescription
questionnaire_idstringtrueThe id of the Questionnaire being answered by the patient. The questionnaire’s use case in charting must be Review of Systems, and it must be set to originate in charting.
answerslist of AnswerfalseThe 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:

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 #

Records that a patient is stopping a medication, along with the reason. See Recording a medication for how it compares to the other medication commands.

Command-specific parameters:

NameTypeRequired to commitDescription
medication_idstringtrueThe id of the Medication being stopped. Must be a medication already recorded on that patient’s chart.
rationalestringfalseThe reason for stopping the medication.
assessment_idstringfalseThe id (a UUID) of an Assessment made in the same note as this command. A cross-note reference or an unknown id is rejected on originate and edit. The link clears automatically if that Assessment is deleted or entered in error, or if a move puts the two in different notes.

Example:

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:

NameTypeRequired to commitDescription
questionnaire_idstringtrueThe id of the Questionnaire being answered by the patient. The questionnaire’s use case in charting must be Structured Assessment, and it must be set to originate in charting.
answerslist of AnswerfalseThe 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:

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:

NameTypeRequired to commitDescription
titlestringtrueThe title or summary of the task.
assign_toTaskAssignertrueSpecifies the assignee (role, team, or individual).
due_datedatefalseDue date for completing the task.
priorityTaskPriority enumfalsePriority of the task. Must be one of TaskPriority.
commentstringfalseAdditional comments or notes about the task.
labelslist[string]falseLabels to apply to the task. Each value is matched (case-insensitive) against an existing TaskLabel by name; values that don’t match an existing label are ignored.
linked_items_urnslist[string]falseURNs for items linked to the task.
assessment_idstringfalseThe id (a UUID) of an Assessment made in the same note as this command. A cross-note reference or an unknown id is rejected on originate and edit. The link clears automatically if that Assessment is deleted or entered in error, or if a move puts the two in different notes.

Enums and Types:

TaskPriority

PriorityDescription
STATThe request should be actioned immediately — highest possible priority. E.g. an emergency.
URGENTThe request should be actioned promptly — higher priority than routine.
ROUTINEThe request has normal priority.

TaskAssigner Type:

KeyTypeRequiredDescription
toAssigneeTypetrueType of assignee (e.g., role, team, etc.).
idintegerfalseThe dbid of the assignee, in the table selected by to: a CareTeamRole when to is ROLE, a Team when to is TEAM, or a Staff when to is STAFF. Omit when to is UNASSIGNED.

AssigneeTypeValueDescription
ROLE"role"Task assigned to a specific CareTeamRole (id is the role’s dbid).
TEAM"team"Task assigned to a specific Team (id is the team’s dbid).
UNASSIGNED"unassigned"Task is unassigned.
STAFF"staff"Task assigned to a specific Staff member (id is the staff member’s dbid).

Example:

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:

NameTypeRequired to commitDescription
report_idslist[string]trueList of UncategorizedClinicalDocument ids to review. Must be documents already on that patient’s chart.
message_to_patientstringfalseMessage to communicate findings to the patient.
communication_methodReportReviewCommunicationMethod enumfalseMethod for patient communication. Must be one of ReportReviewCommunicationMethod.
linked_items_urnslist[string]falseList of URNs for items linked to the review.
commentstringfalseInternal comment about the review.

Example:

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 #

Replaces an existing diagnosis with a different code, carrying the original’s history onto the new condition. See Updating or removing a condition for how it compares to the other condition commands.

Command-specific parameters:

NameTypeRequired to commitDescription
condition_codestringtrueThe ICD-10 code of the existing diagnosis to update. Must match an active Condition on that patient’s chart, including encounter diagnoses recorded with Assess’s show_in_problem_list=False.
new_condition_codestringtrueThe new ICD-10 code to replace the existing diagnosis, looked up via GET /icd/condition/.
backgroundstringfalseBackground information or notes related to the updated diagnosis (max length: 2048 characters).
narrativestringfalseA narrative or explanation about the update (max length: 2048 characters).

Example

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:

NameTypeRequired to commitDescription
goal_idstringtrueThe id of the Goal being updated. Must be a goal on that patient’s chart.
due_datedatetimefalseThe date the goal is due.
achievement_statusAchievementStatus enumfalseThe current achievement status of the goal.
priorityPriority enumfalseThe priority of the goal.
progressstringfalseA narrative about the patient’s progress toward the goal.
assessment_idstringfalseThe id (a UUID) of an Assessment made in the same note as this command. A cross-note reference or an unknown id is rejected on originate and edit. The link clears automatically if that Assessment is deleted or entered in error, or if a move puts the two in different notes.

AchievementStatusValueDescription
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.

PriorityValueDescription
HIGH"high-priority"High priority.
MEDIUM"medium-priority"Medium priority.
LOW"low-priority"Low priority.

Example:

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'
)

VisualExamFinding #

Records a titled image of a clinical observation, with a narrative, on a note. The Visual Exam Finding command must be enabled on the instance to attach an image.

Command-specific parameters:

NameTypeDescription
titlestringThe title of the finding. Up to 255 characters.
narrativestringA description of the observation.
image_upload_keystringThe upload key of a PNG or JPEG image, from a SimpleAPI route declared with file_uploads="stored". See Storing uploaded files. Canvas copies the file to the finding and then deletes the upload, so each key works only once. A key of None attaches no image. Not allowed in a recommendation, such as one on a protocol card.

The command supports originate, edit, delete, commit, and enter_in_error. To read findings, see VisualExamFinding.

Example:

from canvas_sdk.commands import VisualExamFindingCommand

finding = VisualExamFindingCommand(
    note_uuid='8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47',
    title='Rash',
    narrative='Erythematous rash on the left forearm',
    image_upload_key='acme/plugin-uploads/my_plugin/20261014T120000Z-abc-rash.jpg',
)
effect = finding.originate()

In a SimpleAPI handler, set image_upload_key to the key of the uploaded StoredFilePart rather than a literal string.

Example with an uploaded photo:

This SimpleAPI route adds a Visual Exam Finding to a note from a multipart/form-data request that carries the note’s id, a title, a narrative, and the photo. The route uses the API key mixin, so declare the simpleapi-api-key secret in your manifest and set it on your instance:

from http import HTTPStatus

from canvas_sdk.commands import VisualExamFindingCommand
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


class VisualExamFindingAPI(APIKeyAuthMixin, SimpleAPI):
    @api.post("/visual-exam-finding", file_uploads="stored")
    def visual_exam_finding(self) -> list[Response | Effect]:
        form = self.request.form_data()
        image = form["image"]
        if image.error:
            return [JSONResponse({"error": "upload failed"}, status_code=HTTPStatus.BAD_GATEWAY)]

        finding = VisualExamFindingCommand(
            note_uuid=form["note_id"].value,
            title=form["title"].value,
            narrative=form["narrative"].value,
            image_upload_key=image.key,
        )
        return [finding.originate(), JSONResponse({}, status_code=HTTPStatus.ACCEPTED)]

Call the route with a multipart/form-data POST, sending the photo as a file and the API key in the Authorization header:

curl --request POST \
     --url 'https://<instance-name>.canvasmedical.com/plugin-io/api/<plugin-name>/visual-exam-finding' \
     --header 'Authorization: <api-key>' \
     --form 'note_id=8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47' \
     --form 'title=Rash' \
     --form 'narrative=Erythematous rash on the left forearm' \
     --form 'image=@rash.jpg;type=image/jpeg'

The same request from a Python script, uploading a photo saved on disk, using the requests library:

from pathlib import Path

import requests

url = "https://<instance-name>.canvasmedical.com/plugin-io/api/<plugin-name>/visual-exam-finding"
image_path = Path("rash.jpg")

with image_path.open("rb") as image:
    response = requests.post(
        url,
        headers={"Authorization": "<api-key>"},
        data={
            "note_id": "8f4b1e2c-9a3d-4c7e-b1f6-2d5a8c0e3b47",
            "title": "Rash",
            "narrative": "Erythematous rash on the left forearm",
        },
        files={"image": (image_path.name, image, "image/jpeg")},
        timeout=30,
    )

print(response.status_code)

Vitals #

Command-specific parameters:

NameTypeRequired to commitDescription
heightintegerfalseHeight in inches.
weight_lbsintegerfalseWeight in pounds.
weight_ozintegerfalseWeight in ounces.
waist_circumferenceintegerfalseWaist circumference in inches.
body_temperaturefloatfalseBody temperature in Fahrenheit.
body_temperature_siteBodyTemperatureSitefalseSite of body temperature measurement.
blood_pressure_systoleintegerfalseSystolic blood pressure.
blood_pressure_diastoleintegerfalseDiastolic blood pressure.
blood_pressure_position_and_siteBloodPressureSitefalsePosition and site of blood pressure measurement.
pulseintegerfalsePulse rate in beats per minute.
pulse_rhythmPulseRhythmfalseRhythm of the pulse.
respiration_rateintegerfalseRespiration rate in breaths per minute.
oxygen_saturationintegerfalseOxygen saturation in percentage.
supplemental_oxygenSupplementalOxygenfalseType of supplemental oxygen the patient is receiving.
notestringfalseAdditional notes (max length: 150 characters).

For an example of how weight (weight_lbs / weight_oz) and other vitals values are stored and read back via the SDK, see the vitals visualizer example plugin, which retrieves a patient’s weight and displays it in a side modal.

Enums and Types:

BodyTemperatureSiteValueDescription
AXILLARY0Measurement taken from the armpit.
ORAL1Measurement taken from the mouth.
RECTAL2Measurement taken from the rectum.
TEMPORAL3Measurement taken from the forehead.
TYMPANIC4Measurement taken from the ear.

BloodPressureSiteValueDescription
SITTING_RIGHT_UPPER0Sitting position, right upper arm.
SITTING_LEFT_UPPER1Sitting position, left upper arm.
STANDING_RIGHT_UPPER4Standing position, right upper arm.
SUPINE_LEFT_LOWER11Supine position, left lower arm.

PulseRhythmValueDescription
REGULAR0Regular rhythm.
IRREGULARLY_IRREGULAR1Completely irregular rhythm.
REGULARLY_IRREGULAR2Regularly irregular rhythm.

SupplementalOxygenValueDescription
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:

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 NameTypeRequiredDescription
representative_ndcstringtrueNational Drug Code (NDC) representing the medication.
ncpdp_quantity_qualifier_codestringtrueNCPDP code indicating the quantity qualifier.
descriptionstringfalseThe 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: representative_ndc ← representative_ndc, ncpdp_quantity_qualifier_code ← erx_ncpdp_script_quantity_qualifier_code, and description ← clinical_quantity_description.

Usage Example:

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
)

Looking up representative_ndc and ncpdp_quantity_qualifier_code: When a Prescribe command uses an fdb_code, type_to_dispense requires a ClinicalQuantity with both representative_ndc and ncpdp_quantity_qualifier_code. You can obtain these values from the ontologies service. The FDB code is the med_medication_id returned by a medication search; query the grouped-medication endpoint directly by that ID to read its clinical quantities:

from canvas_sdk.utils.http import ontologies_http

med_medication_id = 436095  # this is the fdb_code
response_json = ontologies_http.get_json(f"/fdb/grouped-medication/{med_medication_id}/").json()

A single FDB code can contain multiple clinical_quantities, so filter to the correct one by matching its clinical_quantity_description to your medication’s form. The fields map to ClinicalQuantity as follows: representative_ndc → representative_ndc, and erx_ncpdp_script_quantity_qualifier_code → ncpdp_quantity_qualifier_code.

ServiceProvider #

ServiceProvider represents detailed information about healthcare service providers, used in referral, imaging order, and Order DME commands.

Field NameTypeDescription
first_namestringService provider’s first name (max length 512)
last_namestringService provider’s last name (max length 512)
specialtystringProvider’s specialty (max length 512)
practice_namestringName of the practice (max length 512)
business_faxOptional[string]Business fax number (optional, max length 512)
business_phoneOptional[string]Business phone number (optional, max length 512)
business_addressOptional[string]Business address (optional, max length 512)
notesOptional[string]Additional notes (optional, max length 512)

Usage Example:

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 SystemSystem URIDescription
SNOMEDhttp://snomed.info/sctSystematized Nomenclature of Medicine Clinical Terms
RXNORMhttp://www.nlm.nih.gov/research/umls/rxnormRxNorm — standardized nomenclature for medications
LOINChttp://loinc.orgLogical Observation Identifiers Names and Codes (labs/observations)
FDBhttp://www.fdbhealth.com/First Databank drug knowledge base
ICD10ICD-10International Classification of Diseases, 10th Revision
CVXhttp://hl7.org/fhir/sid/cvxCDC codes for administered vaccines
CPThttp://www.ama-assn.org/go/cptCurrent Procedural Terminology (AMA procedure codes)
NUCChttp://www.nucc.org/National Uniform Claim Committee provider taxonomy codes
NDChttp://hl7.org/fhir/sid/ndcNational Drug Code
HCPCShttp://www.cms.gov/medicare/coding/medhcpcsgeninfoHealthcare Common Procedure Coding System
UNITS_OF_MEASUREhttp://unitsofmeasure.orgUnified Code for Units of Measure (UCUM)
FULLSCRIPThttp://fullscript.comFullscript supplement/dispensary code system
UNSTRUCTUREDUNSTRUCTUREDCanvas-specific system for unstructured or custom codes

Usage Example:

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 NameTypeDescription
systemstringThe coding system identifier (e.g., ICD-10, SNOMED)
codestringThe specific code within the system
displayOptional[string]Human-readable description of the code

Usage Example:

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.

from canvas_sdk.commands.commands.review import ReportReviewCommunicationMethod
Communication MethodValueDescription
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