diff --git a/docs/api_changes.md b/docs/api_changes.md
index 79b512f..5fd4914 100644
--- a/docs/api_changes.md
+++ b/docs/api_changes.md
@@ -1,3 +1,4 @@
+##
 This document keeps a record of all changes to Moonraker's web APIs.
 
 ### March 15th 2021
diff --git a/docs/configuration.md b/docs/configuration.md
index 272cdaa..2f10aee 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -1,29 +1,17 @@
+#
 This document describes Moonraker's full configuration.  As this file
-references configuration for both Klipper (printer.cfg) and Moonraker
-(moonraker.conf), each example contains a commment indicating which
-configuration file is being refrenenced.
+references configuration for both Klipper (`printer.cfg`) and Moonraker
+(`moonraker.conf`), each example contains a commment indicating which
+configuration file is being refrenenced. A basic
+[sample configuration](./moonraker.conf) in the `docs` directory.
 
-# Primary Configuration
-The sections outlined here are required for Moonraker to function. A
-minimal functional configuration might look like the following:
-```
-# moonraker.conf
+## `[server]`
 
-[server]
-host: 0.0.0.0
-port: 7125
-enable_debug_logging: True
-config_path: ~/.klippy_config
+The `[server]` section provides essential configuration for Moonraker
+and its core components.  This section is requrired.
 
-[authorization]
-enabled: True
-trusted_clients:
- 192.168.1.0/24
-```
+```ini
 
-## server
-
-```
 # moonraker.conf
 
 [server]
@@ -61,9 +49,12 @@ temperature_store_size: 1200
 gcode_store_size:  1000
 #   The maximum number "gcode lines" to store.  The default is 1000.
 ```
-## authorization
+## `[authorization]`
 
-```
+The `[authorization]` section provides configuration for Moonraker's
+authorization module. This section is required.
+
+```ini
 # moonraker.conf
 
 [authorization]
@@ -103,38 +94,33 @@ cors_domains:
 #   well.  If this option is not specified then CORS is disabled.
 ```
 
-# Plugin Configuration
-The sections outlined here are optional and enable additional
-functionality in moonraker.
-
-## Octoprint compatibility
+## `[octoprint_compat]`
 Enables partial support of Octoprint API is implemented with the purpose of
 allowing uploading of sliced prints to a moonraker instance.
 Currently we support Slic3r derivatives and Cura with Cura-Octoprint.
 
-```
+```ini
 # moonraker.conf
 
 [octoprint_compat]
 ```
 
-## History
-Enables moonraker to track and store print history. To enable this plugin, add
-the configuration below.
+## `[history]`
+Enables print history tracking.
 
-```
+```ini
 # moonraker.conf
 
 [history]
 ```
 
-## paneldue
+## `[paneldue]`
 Enables PanelDue display support.  The PanelDue should be connected to the
 host machine, either via the machine's UART GPIOs or through a USB-TTL
 converter.  Currently PanelDue Firmware Version 1.24 is supported.  Other
 releases may not behave correctly.
 
-```
+```ini
 # moonraker.conf
 
 [paneldue]
@@ -168,7 +154,7 @@ to specify commands (either built in or gcode_macros) that will show up
 in the PanelDue's "macro" menu.
 
 Note that buzzing the piezo requires the following gcode_macro in `printer.cfg`:
-```
+```ini
 # printer.cfg
 
 [gcode_macro PANELDUE_BEEP]
@@ -182,11 +168,11 @@ gcode:
                              duration=DURATION|float)}
 ```
 
-## power
+## `[power]`
 Enables device power control.  Currently GPIO (relays), TPLink Smartplug,
 and Tasmota (via http) devices are supported.
 
-```
+```ini
 # moonraker.conf
 
 [power device_name]
@@ -265,7 +251,7 @@ password:
 
 ```
 Below are some potential examples:
-```
+```ini
 # moonraker.conf
 
 [power printer]
@@ -302,7 +288,7 @@ password: password2
 
 It is possible to toggle device power from the Klippy host, this can be done
 with a gcode_macro, such as:
-```
+```ini
 # printer.cfg
 
 [gcode_macro POWER_OFF_PRINTER]
@@ -314,7 +300,7 @@ gcode:
 The `POWER_OFF_PRINTER` gcode can be run to turn off the "printer" device.
 This could be used in conjunction with Klipper's idle timeout to turn the
 printer off when idle with a configuration similar to that of below:
-```
+```ini
 # printer.cfg
 
 [delayed_gcode delayed_printer_off]
@@ -331,12 +317,12 @@ gcode:
   UPDATE_DELAYED_GCODE ID=delayed_printer_off DURATION=60
 ```
 
-## update_manager
+## `[update_manager]`
 This enables moonraker's update manager.  Note that updates can only be
 performed on pristine git repos.  Repos that have been modified on
 disk or cloned from unofficial sources are not supported.
 
-```
+```ini
 # moonraker.conf
 
 [update_manager]
@@ -367,7 +353,7 @@ There are two types of update manager clients and each will be detailed
 separately. The first one is targeted towards releases that do not need a
 service restart such as Fluidd/Mainsail.
 
-```
+```ini
 # moonraker.conf
 
 [update_manager client client_name]
@@ -387,7 +373,7 @@ persistent_files:
 This second example is for git repositories that have a service that need
 updating.
 
-```
+```ini
 # moonraker.conf
 
 # service_name must be the name of the systemd service
diff --git a/docs/contributing.md b/docs/contributing.md
index 3c166ce..603183b 100644
--- a/docs/contributing.md
+++ b/docs/contributing.md
@@ -8,24 +8,64 @@ are near those of Klipper:
 
 - All source files should begin with a copyright notice in the following
   format:
-  ```
-  # Module name and brief description of module
-  #
-  # Copyright (C) 2020 YOUR NAME <YOUR EMAIL ADDRESS>
-  #
-  # This file may be distributed under the terms of the GNU GPLv3 license
-  ```
+
+        # Module name and brief description of module
+        #
+        # Copyright (C) 2021 YOUR NAME <YOUR EMAIL ADDRESS>
+        #
+        # This file may be distributed under the terms of the GNU GPLv3 license
+
 - No line in the source code or documentation should exceed 80 characters.
   Be sure there is no trailing whitespace.
 - Each Commit message should be in the following format:
-  ```
-  module name: brief description of commit
 
-  More detailed explanation if necessary.
+        module name: brief description of commit
+
+        More detailed explanation if necessary.
+
+        Signed-off-by:  Your Name <your email address>
 
-  Signed-off-by:  Your Name <your email address>
-  ```
 - By signing off on commits, you acknowledge that you agree to the
-  [developer certificate of origin](developer-certificate-of-origin).
-  As with Klipper, this must contain your real name and a current
+  [developer certificate of origin](../developer-certificate-of-origin)
+  shown below. Your signature must contain your real name and a current
   email address.
+
+```text
+Developer Certificate of Origin
+Version 1.1
+
+Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
+1 Letterman Drive
+Suite D4700
+San Francisco, CA, 94129
+
+Everyone is permitted to copy and distribute verbatim copies of this
+license document, but changing it is not allowed.
+
+
+Developer's Certificate of Origin 1.1
+
+By making a contribution to this project, I certify that:
+
+(a) The contribution was created in whole or in part by me and I
+    have the right to submit it under the open source license
+    indicated in the file; or
+
+(b) The contribution is based upon previous work that, to the best
+    of my knowledge, is covered under an appropriate open source
+    license and I have the right under that license to submit that
+    work with modifications, whether created in whole or in part
+    by me, under the same open source license (unless I am
+    permitted to submit under a different license), as indicated
+    in the file; or
+
+(c) The contribution was provided directly to me by some other
+    person who certified (a), (b) or (c) and I have not modified
+    it.
+
+(d) I understand and agree that this project and the contribution
+    are public and that a record of the contribution (including all
+    personal information I submit with it, including my sign-off) is
+    maintained indefinitely and may be redistributed consistent with
+    this project or the open source license(s) involved.
+```
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..13b7523
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,13 @@
+# Welcome to Moonraker Documentation
+
+Users should refer to the [Installation](installation.md) and
+[Configuration](configuration.md) sections for documentation on how
+to install and configure Moonraker.
+
+Client developers may refer to the [Client API](web_api.md)
+documentation.
+
+Internal API documentation for back-end contributors is forthcoming.
+In the meantime developers should refer to the
+[contibuting](contributing.md) section for basic contribution
+guidelines prior to creating a pull request.
diff --git a/docs/installation.md b/docs/installation.md
index 2e83c39..0d29a88 100644
--- a/docs/installation.md
+++ b/docs/installation.md
@@ -6,14 +6,39 @@ may work, however they may need a custom install script.  Moonraker
 requires Python 3.7 or greater, verify that your distribution's
 Python 3 packages meet this requirement.
 
-Klipper should be installed prior to installing Moonraker.  Please see
-[Klipper's Documention](https://github.com/KevinOConnor/klipper/blob/master/docs/Installation.md)
-for instructions on how to do this.  After installation you should make
-sure that the [prerequistes](#prerequisites-klipper-configuration) are configured.
+### Installing Klipper
 
-After Klipper is installed, you need to modify its "default" file.  This file
-contains klipper's command line arguments, and you must add an argument that
-instructs Klippy to create a Unix Domain socket:
+Klipper should be installed prior to installing Moonraker.  Please see
+[Klipper's Documention](https://klipper3d.com/Overview.html) for details.
+After installing Klipper you should make sure to add Moonraker's
+[configuration requirements](#klipper-configuration-requirements).
+
+### Klipper Configuration Requirements
+
+Moonraker depends on the following Klippy extras for full functionality:
+
+- `[virtual_sdcard]`
+- `[pause_resume]`
+- `[display_status]`
+
+If you have a `[filament_switch_sensor]` configured then `[pause_resume]` will
+automatically be loaded.  Likewise, if you have a `[display]` configured then
+`[display_status]` will be automatically loaded.  If your configuration is
+missing one or both, you can simply add the bare sections to `printer.cfg`:
+```ini
+[pause_resume]
+
+[display_status]
+
+[virtual_sdcard]
+path: ~/gcode_files
+```
+
+### Enabling the Unix Socket
+
+After Klipper is installed it may be necessary to modify its `defaults` file in
+order to enable the Unix Domain Socket.  Begin by opening the file in your
+editor of choice, for example:
 ```
 sudo nano /etc/default/klipper
 ```
@@ -28,7 +53,7 @@ KLIPPY_EXEC=/home/pi/klippy-env/bin/python
 KLIPPY_ARGS="/home/pi/klipper/klippy/klippy.py /home/pi/printer.cfg -l /tmp/klippy.log"
 ```
 
-You need to add `-a /tmp/klippy_uds` to KLIPPY_ARGS:
+Add `-a /tmp/klippy_uds` to KLIPPY_ARGS:
 ```
 # Configuration for /etc/init.d/klipper
 
@@ -38,11 +63,16 @@ KLIPPY_EXEC=/home/pi/klippy-env/bin/python
 
 KLIPPY_ARGS="/home/pi/klipper/klippy/klippy.py /home/pi/printer.cfg -l /tmp/klippy.log -a /tmp/klippy_uds"
 ```
+
+**Note: Your installation of Klipper may use systemd instead of
+the default LSB script.  In this case, you need to modify the
+klipper.service file.**
+
 You may also want to take this opportunity to change the location of
-printer.cfg to match Moonraker's "config_path" option (see the
+printer.cfg to match Moonraker's `config_path` option (see the
 [configuration document](configuration.md#primary-configuration)
 for more information on the config_path). For example, if the `config_path`
-option is set to  `~/printer_config`, your klipper defaults file might look
+is set to  `~/printer_config`, your klipper defaults file might look
 like the following:
 ```
 # Configuration for /etc/init.d/klipper
@@ -61,17 +91,19 @@ mkdir printer_config
 mv printer.cfg printer_config
 ```
 
-You can now install the Moonraker application:
+### Installing Moonraker
+
+Begin by cloning the git respository:
+
 ```
 cd ~
 git clone https://github.com/Arksine/moonraker.git
 ```
 
-Prior to installation it is a good idea to create
-[moonraker.conf](configuration.md).  If you are using the `config_path`,
-create it in the specified directory otherwise create it in the HOME
-directory.  A sample `moonraker.conf` may be found in the `docs` folder
-of this repo.
+Now is a good time to create [moonraker.conf](configuration.md).  If you are
+using the `config_path`, create it in the specified directory otherwise create
+it in the HOME directory.  The [sample moonraker.conf](./moonraker.conf) in
+the `docs` directory may be used as a starting point.
 
 For a default installation run the following commands:
 ```
@@ -87,28 +119,32 @@ cd ~/moonraker/scripts
 
 The install script has a few command line options that may be useful,
 particularly for those upgrading:
-- -r\
-  This will rebuild the virtual environment for existing installations.
+
+- `-r`:
+  Rebuilds the virtual environment for existing installations.
   Sometimes this is necessary when a dependency has been added.
-- -f\
-  This will tell the script to overwrite Moonraker's "defaults" file.
-  By default the script will not modify the "defaults" file if it is
-  detected as present.
-- -c /path/to/moonraker.conf\
-  This allows the user to specify the path to Moonraker's config file.
-  The default location is `/home/<user>/moonraker.conf`.
+- `-f`:
+  Force an overwrite of Moonraker's systemd script. By default the
+  the systemd script will not be modified if it exists.
+- `-c /home/pi/moonraker.conf`:
+  Specifies the path to Moonraker's config file. The default location
+  is `/home/<user>/moonraker.conf`.  When using this option to modify
+  an existing installation it is necessary to add `-f` as well.
 
 When the script completes it should start both Moonraker and Klipper. In
-`klippy.log` you should find the following entry:\
-`webhooks client <uid>: Client info {'program': 'Moonraker', 'version': '<version>'}`\
+`/tmp/klippy.log` you should find the following entry:
+
+`webhooks client <uid>: Client info {'program': 'Moonraker', 'version': '<version>'}`
 
 Now you may install a client, such as
 [Mainsail](https://github.com/meteyou/mainsail) or
 [Fluidd](https://github.com/cadriel/fluidd)
 
-## Command line Usage
-The configuration and log file paths may be specified via the command
-line.
+### Command line usage
+
+This section is intended for users that need to write their own
+installation script.  Detailed are the command line arguments
+available to Moonraker:
 ```
 usage: moonraker.py [-h] [-c <configfile>] [-l <logfile>] [-n]
 
@@ -129,7 +165,7 @@ The default configuration is:
 - logging to a file is enabled
 
 If one needs to start moonraker without generating a log file, the
-'-n' option may be used, for example:
+`-n` option may be used, for example:
 ```
 ~/moonraker-env/bin/python ~/moonraker/moonraker/moonraker.py -n -c /path/to/moonraker.conf
 ```
@@ -141,28 +177,11 @@ These options may be changed by editing
 `/etc/systemd/system/moonraker.service`.  The `install-moonraker.sh` script
 may also be used to modify the config file location.
 
-## Prerequisites (Klipper Configuration)
+### Additional Notes
 
-Moonraker depends on the following Klippy extras for full functionality:
-- [virtual_sdcard]
-- [pause_resume]
-- [display_status]
-
-If you have a `filament_switch_sensor` configured then `pause_resume` will
-automatically be loaded.  Likewise, if you have a `display` configured then
-`display_status` will be automatically loaded.  If your configuration is
-missing one or both, you can simply add the bare sections to printer.cfg:
-```
-[pause_resume]
-
-[display_status]
-
-[virtual_sdcard]
-path: ~/gcode_files
-```
-NOTES:
-- Make sure that Moonraker (and Klipper) has read and write access to the
-  directory set in the `path` option for the `virtual_sdcard`.
+- Make sure that Moonraker and Klipper both have read and write access to the
+  directory set in the `path` option for the `[virtual_sdcard]` in
+  `printer.cfg`.
 - Upon first starting Moonraker is not aware of the gcode file path, thus
   it cannot serve gcode files, add directories, etc.  After Klippy enters
   the "ready" state it sends Moonraker the gcode file path.
diff --git a/docs/printer_objects.md b/docs/printer_objects.md
index 9f8d180..82dd27d 100644
--- a/docs/printer_objects.md
+++ b/docs/printer_objects.md
@@ -1,3 +1,4 @@
+#
 As mentioned in the API documentation, it is possible to
 [query](web_api.md#query-printer-object-status) or
 [subscribe](web_api.md#subscribe-to-printer-object-status)
@@ -17,31 +18,32 @@ that reports state which can be queried.
 ## webhooks
 ```json
 {
-  state: "startup",
-  state_message: "message"
+  "state": "startup",
+  "state_message": "message"
 }
 ```
 The `webhooks` object contains the current printer state and the current
 state message.  These fields match those returned via the `/printer/info`
 endpoint.  This is provided as a convience, clients may subscribe to `webhooks`
 so they are asynchonously notified of a change to printer state.  The `state`
-may be "startup", "ready", "shutdown", or "error".  The `state_message`
+may be `startup`, `ready`, `shutdown`, or `error`.  The `state_message`
 contains a message specific to the current printers state.
 
 ## gcode_move
 ```json
 {
-  speed_factor: 1.0,
-  speed: 100.0,
-  extrude_factor: 1.0,
-  absolute_coordinates: true,
-  absolute_extrude: false,
-  homing_origin: [0.0, 0.0, 0.0, 0.0],
-  position: [0.0, 0.0, 0.0, 0.0],
-  gcode_position: [0.0, 0.0, 0.0, 0.0]
+    "speed_factor": 1.0,
+    "speed": 100.0,
+    "extrude_factor": 1.0,
+    "absolute_coordinates": true,
+    "absolute_extrude": false,
+    "homing_origin": [0.0, 0.0, 0.0, 0.0],
+    "position": [0.0, 0.0, 0.0, 0.0],
+    "gcode_position": [0.0, 0.0, 0.0, 0.0]
 }
 ```
 The `gcode_move` object reports the current gcode state:
+
 - `speed_factor`: AKA "feedrate", this is the current speed multiplier
 - `speed`: The current gcode speed in mm/s.
 - `extrude_factor`: AKA "extrusion multiplier".
@@ -59,24 +61,25 @@ The `gcode_move` object reports the current gcode state:
   the most recent "G1" or "G0" processed assuming the machine is
   using absolute coordinates.
 
-Note: The printer's actual movement may lag behind the reported positional
+Note: The printer's actual movement will lag behind the reported positional
 coordinates due to lookahead.
 
 ## toolhead
 ```json
 {
-  homed_axes: "xyz",
-  print_time: 0.0,
-  estimated_print_time: 0.0,
-  extruder: "extruder",
-  position: [0.0, 0.0, 0.0, 0.0],
-  max_velocity: 500.0,
-  max_accel: 3000.0,
-  max_accel_to_decel: 1500.0,
-  square_corner_velocity: 5.0
+    "homed_axes": "xyz",
+    "print_time": 0.0,
+    "estimated_print_time": 0.0,
+    "extruder": "extruder",
+    "position": [0.0, 0.0, 0.0, 0.0],
+    "max_velocity": 500.0,
+    "max_accel": 3000.0,
+    "max_accel_to_decel": 1500.0,
+    "square_corner_velocity": 5.0
 }
 ```
 The `toolhead` object reports state of the current tool:
+
 - `homed_axes`: a string containing the axes that are homed. If no axes
   are homed, returns a null string.
 - `print_time`: internal value, not generally useful for clients
@@ -102,16 +105,21 @@ Note:  `max_velocity`, `max_accel`, `max_accel_to_decel`, and
 ## configfile
 ```json
 {
-  config: {},
-  save_config_pending: false
+    "config": {},
+    "settings": {},
+    "save_config_pending": false
 }
 ```
 The `configfile` object reports printer configuration state:
+
 - `config`:  This is an object containing the configuration as read from
   printer.cfg.  Each config section will be an object containing the
-  configured options.  Option values will ALWAYS be reported as
+  configured options.  Values will ALWAYS be reported as
   strings.  Note that default values are not reported, only options
   configured in printer.cfg are present.
+- `settings`:  Similar to `config`, however this object includes default
+  values that may not have been included in `printer.cfg`.  It is possible
+  for a value to be a string, integer, boolean, or float.
 - `save_config_pending`: True if the printer has taken an action which
   has updated the internal configuration (ie: PID calibration, probe
   calibration, bed mesh calibration).  This allows clients to present
@@ -119,75 +127,90 @@ The `configfile` object reports printer configuration state:
   save the configuration to printer.cfg and restart the Klippy Host.
 
 ## extruder
-*Enabled when [extruder] is included in printer.cfg*
+*Enabled when `[extruder]` is included in printer.cfg*
 Note: If multiple extruders are configured, extruder 0 is available as
 `extruder`, extruder 1 as `extruder1` and so on.
 ```json
 {
-  temperature: 0.0,
-  target: 0.0
-  pressure_advance: 0.0,
-  smooth_time: 0.0
+    "temperature": 0.0,
+    "target": 0.0,
+    "power": 0.0,
+    "pressure_advance": 0.0,
+    "smooth_time": 0.0
 }
 ```
 The `extruder` object reports state of an extruder:
+
 - `temperature`:  The extruder's current temperature (in C).
 - `target`:  The extruder's target temperature (in C).
+- `power`: The current pwm value applied to the heater.  This value is
+  expressed as a percentage from 0.0 to 1.0.
 - `pressure_advance`:  The extruder's current pressure advance value.
 - `smooth_time`:  The currently set time range to use when calculating the
   average extruder velocity for pressure advance.
 
 ## heater_bed
-*Enabled when [heater_bed] is included in printer.cfg*
+*Enabled when `[heater_bed]` is included in printer.cfg*
 ```json
 {
-  temperature: 0.0,
-  target: 0.0
+    "temperature": 0.0,
+    "target": 0.0,
+    "power": 0.0,
 }
 ```
 The `heater_bed` object reports state of the heated bed:
+
 - `temperature`:  The bed's current temperature
 - `target`:  The bed's target temperature
+- `power`: The current pwm value applied to the heater.  This value is
+  expressed as a percentage from 0.0 to 1.0.
 
 ## fan
-*Enabled when [fan] is included in printer.cfg*
+*Enabled when `[fan]` is included in printer.cfg*
 ```json
 {
-  speed: 0.0
+    "speed": 0.0,
+    "rpm": 4000
 }
 ```
 The `fan` object returns state of the part cooling fan:
+
 - `speed`:  The current fan speed.  This is reported as a
   percentage of maximum speed in the range of 0.0 - 1.0.
+- `rpm`:  The fan's revolutions per minute if the tachometer
+  pin has been configured.  Will report `null` if no tach
+  has been configured.
 
 ## idle_timeout
 ```json
 {
-  state: "Idle",
-  printing_time: 0.0
+   "state": "Idle",
+   "printing_time": 0.0
 }
 ```
 
 The `idle_timeout` object reports the idle state of the printer:
-- `state`: Can be "Idle", "Ready", or "Printing".  The printer will
-  transition to the "Printing" state whenever a gcode is issued that
+
+- `state`: Can be `Idle`, `Ready`, or `Printing`.  The printer will
+  transition to the `Printing` state whenever a gcode is issued that
   commands the tool, this includes manual commands.  Thus this should
   not be used to determine if a gcode file print is in progress.  It can
   however be used to determine if the printer is busy.
 - `printing_time`:  The amount of time the printer has been in the
-  "Printing" state.  This is reset to 0 whenever the printer transitions
-  from "Printing" to "Ready".
+  `Printing` state.  This is reset to 0 whenever the printer transitions
+  from `Printing` to `Ready`.
 
 ## virtual_sdcard
-*Enabled when [virtual_sdcard] is included in printer.cfg*
+*Enabled when `[virtual_sdcard]` is included in printer.cfg*
 ```json
 {
-  progress: 0.0,
-  is_active: false,
-  file_position: 0
+    "progress": 0.0,
+    "is_active": false,
+    "file_position": 0
 }
 ```
 The `virtual_sdcard` object reports the state of the virtual sdcard:
+
 - `progress`: The print progress reported as a percentage of the file
   read, in the range of 0.0 - 1.0.
 - `is_active`: Returns true if the virtual sdcard is currently processing
@@ -196,23 +219,24 @@ The `virtual_sdcard` object reports the state of the virtual sdcard:
 - `file_position`:  The current file position in bytes.  This will always
   be an integer value
 
-Note:  `progress` and `file_position` will persist after a print has
+**Note:  `progress` and `file_position` will persist after a print has
 paused, completed, or errored.  They are cleared when the user issues
-a SDCARD_RESET_FILE gcode or when a new print has started.
+a SDCARD_RESET_FILE gcode or when a new print has started.**
 
 ## print_stats
-*Enabled when [virtual_sdcard] is included in printer.cfg*
+*Enabled when `[virtual_sdcard]` is included in printer.cfg*
 ```json
 {
-  filename: "",
-  total_duration: 0.0,
-  print_duration: 0.0,
-  filament_used: 0.0,
-  state: "standby",
-  message: ""
+    "filename": "",
+    "total_duration": 0.0,
+    "print_duration": 0.0,
+    "filament_used": 0.0,
+    "state": "standby",
+    "message": ""
 }
 ```
-The `print_stats` object reports "virtual_sdcard" print state:
+The `print_stats` object reports `virtual_sdcard` print state:
+
 - `filename`:  The name of the current file loaded.  This will be a null
   string if no file is loaded.  Note that name is a path relative to the
   gcode folder, thus if the file is located in a subdirectory it would
@@ -228,52 +252,55 @@ The `print_stats` object reports "virtual_sdcard" print state:
 - `message`:  If an error is detected, this field contains the error
   message generated.  Otherwise it will be a null string.
 
-Note: After a print has started all of the values above will persist until
-the user issues a SDCARD_RESET_FILE gcode or when a new print has started.
+**Note: After a print has started all of the values above will persist until
+the user issues a SDCARD_RESET_FILE gcode or when a new print has started.**
 
 ## display_status
-*Enabled when [display] or [display_status] is included in printer.cfg*
+*Enabled when `[display]` or `[display_status]` is included in printer.cfg*
 ```json
 {
-  message: "",
-  progress: 0.0
+    "message": "",
+    "progress": 0.0
 }
 ```
 The `display_status` object contains state typically used to update displays:
+
 - `message`:  The message set by a M117 gcode.  If no message is set this will
   be a null string.
 - `progress`:  The percentage of print progress, as reported by M73.  This
   will be in the range of 0.0 - 1.0.  If no M73 has been issued this value
-  will fallback to the eqivalent of `virtual_sdcard.progess`.  Note that
+  will fallback to the eqivalent of `virtual_sdcard.progress`.  Note that
   progress updated via M73 has a timeout.  If no M73 is received after 5
   seconds, `progress` will be set to the fallback value.
 
 ## temperature_sensor sensor_name
-*Enabled when [temperature_sensor sensor_name] is included in printer.cfg.  It is*
-*possible for multiple temperature sensors to be configured.*
+*Enabled when `[temperature_sensor sensor_name]` is included in printer.cfg.
+It is possible for multiple temperature sensors to be configured.*
 ```json
 {
-  temperature: 0.0,
-  measured_min_temp: 0.0,
-  measured_max_temp: 0.0
+    "temperature": 0.0,
+    "measured_min_temp": 0.0,
+    "measured_max_temp": 0.0
 }
 ```
 A `temperature_sensor` reports the following state:
+
 - `temperature`:  Sensor's current reported temperature
 - `measured_min_temp`: The mimimum temperature read from the sensor
 - `measured_max_temp`: The maximum temperature read from the sensor
 
 ## temperature_fan fan_name
-*Enabled when [temperature_fan fan_name] is included in printer.cfg.  It is*
-*possible for multiple temperature fans to be configured.*
+*Enabled when `[temperature_fan fan_name]` is included in printer.cfg.  It is
+possible for multiple temperature fans to be configured.*
 ```json
 {
-  speed: 0.0,
-  temperature: 0.0,
-  target: 0.0
+    "speed": 0.0,
+    "temperature": 0.0,
+    "target": 0.0
 }
 ```
 A `temperature_fan` reports the following state:
+
 - `speed`:  Current fan speed as a percentage of maximum speed, reported
   in the range of 0.0 - 1.0
 - `temperature`:  Currently reported temperature of the sensor associated
@@ -281,48 +308,51 @@ A `temperature_fan` reports the following state:
 - `target`:  The current target temperature for the `temperature_fan`.
 
 ## filament_switch_sensor sensor_name
-*Enabled when [filament_switch_sensor sensor_name] is included in printer.cfg.*
-*It is possible for multiple filament sensors to be configured.*
+*Enabled when `[filament_switch_sensor sensor_name]` is included in
+printer.cfg.  It is possible for multiple filament sensors to be configured.*
 ```json
 {
-  filament_detected: false,
-  enabled: true
+    "filament_detected": false,
+    "enabled": true
 }
 ```
 A `filament_switch_sensor` reports the following state:
+
 - `filament_detected`:  Set to true if the switch detects filament, otherwise
   false
 - `enabled`: Set to true if the sensor is currently enabled, otherwise false
 
 ## output_pin pin_name
-*Enabled when [output_pin pin_name] is included in printer.cfg.*
-*It is possible for multiple output pins to be configured.*
+*Enabled when `[output_pin pin_name]` is included in printer.cfg.
+It is possible for multiple output pins to be configured.*
 ```json
 {
-  value: 0.0
+    "value": 0.0
 }
 ```
 An `output_pin` reports the following state:
+
 - `value`: The currently set value of the pin, in the range of 0.0 - 1.0.
   A digital pin will always be 0 or 1, whereas a pwm pin may report a value
   across the entire range.
 
 ## bed_mesh
-*Enabled when [bed_mesh] is included in printer.cfg.*
+*Enabled when `[bed_mesh]` is included in printer.cfg.*
 ```json
 {
-  profile_name: "",
-  mesh_min: [0.0, 0.0],
-  mesh_max: [0.0, 0.0],
-  probed_matrix: [[]],
-  mesh_matrix: [[]]
+    "profile_name": "",
+    "mesh_min": [0.0, 0.0],
+    "mesh_max": [0.0, 0.0],
+    "probed_matrix": [[]],
+    "mesh_matrix": [[]]
 }
 ```
 The `bed_mesh` printer object reports the following state:
+
 - `profile_name`:  The name of the currently loaded profile.  If no profile is
-  loaded then this will report a null string.  If the user is not using bed_mesh
-  profile management then this will report "default" after mesh calibration
-  completes.
+  loaded then this will report a null string.  If the user is not using
+  bed_mesh profile management then this will report `default` after mesh
+  calibration completes.
 - `mesh_min`: [X, Y] - The minimum x and y coordinates of the mesh.
 - `mesh_max`: [X, Y] - The maximum x and y coordinates of the mesh.
 - `probed_matrix`:  A 2 dimensional array representing the matrix of probed
@@ -330,15 +360,15 @@ The `bed_mesh` printer object reports the following state:
 - `mesh_matrix`: A 2 dimension array representing the interpolated mesh.  If
   no matrix has been generated the result is `[[]]`.
 
-Note: See [web_api.md](web_api.md##bed-mesh-coordinates) for an example
-of how to use this information to generate (X,Y,Z) coordinates.
+**Note: See [web_api.md](web_api.md##bed-mesh-coordinates) for an example
+of how to use this information to generate (X,Y,Z) coordinates.**
 
 ## gcode_macro macro_name
-*Enabled when [gcode_macro macro_name] is included in printer.cfg.*
-*It is possible for multiple gcode macros to be configured.*
+*Enabled when `[gcode_macro macro_name]` is included in printer.cfg.
+It is possible for multiple gcode macros to be configured.*
 
-Gcode macros will report the state of configured "variables".
+Gcode macros will report the state of configured `variables`.
 While user defined macros likely won't report state that is useful
 for a client, it is possible for client developers to recommend or
 request a specific gcode_macro configuration, then have the client
-take action based on the variables reported by the macro.
\ No newline at end of file
+take action based on the variables reported by the macro.
diff --git a/docs/user_changes.md b/docs/user_changes.md
index e0d18f9..b321054 100644
--- a/docs/user_changes.md
+++ b/docs/user_changes.md
@@ -1,3 +1,4 @@
+##
 This file will track changes that require user intervention,
 such as a configuration change or a reinstallation.
 
@@ -33,10 +34,10 @@ such as a configuration change or a reinstallation.
   This changes requires the user to rerun the install script.  If
   `moonraker.conf` is not located in the home directory, the command
   will looks something like the following:
-  ```
-  cd ~/moonraker
-  ./scripts/install-moonraker.sh -f -c /home/pi/klipper_config/moonraker.conf
-  ```
+
+        cd ~/moonraker
+        ./scripts/install-moonraker.sh -f -c /home/pi/klipper_config/moonraker.conf
+
   Otherwise you can run the install script with no arguments.
 
 ### November 19th 2020
@@ -49,24 +50,24 @@ such as a configuration change or a reinstallation.
   will update the defaults file wih the new path.
 - New dependencies have been added to Moonraker which require reinstallation.
   Run the following command to reinstall and rebuild the virtualenv:
-  ```
-  ~/moonraker/scripts/install-moonraker.sh -r
-  ```
+
+        ~/moonraker/scripts/install-moonraker.sh -r
+
 - The power plugin configuration has changed.  See the
   [install guide](installation.md#power-control-plugin) for
   details on the new configuration.
 - Users transitioning from the previous version of the power plugin will need
   to unexport any curently used pins.  For example, the following command
   may be used to unexport pin 19:
-  ```
-  echo 19 > /sys/class/gpio/unexport
-  ```
+
+        echo 19 > /sys/class/gpio/unexport
+
   Alternatively one may reboot the machine after upgrading:
-  ```
-  cd ~/moonraker/
-  git pull
-  ~/moonraker/scripts/install-moonraker.sh -r
-  sudo reboot
-  ```
+
+    cd ~/moonraker/
+    git pull
+    ~/moonraker/scripts/install-moonraker.sh -r
+    sudo reboot
+
   Make sure that the power plugin configuration has been updated prior
   to rebooting the machine.
diff --git a/docs/web_api.md b/docs/web_api.md
index 3cdf305..eba295b 100644
--- a/docs/web_api.md
+++ b/docs/web_api.md
@@ -1,7 +1,7 @@
-# API
+#
 
 Most API methods are supported over both the Websocket and HTTP transports.
-File Transfer and "/access" requests are only available over HTTP. The
+File Transfer and `/access` requests are only available over HTTP. The
 Websocket is required to receive server generated events such as gcode
 responses.  For information on how to set up the Websocket, please see the
 Appendix at the end of this document.
@@ -26,7 +26,9 @@ this document exclusively illustrates arguments via the query string.
 
 All successful HTTP requests will return a json encoded object in the form of:
 
-`{result: <response data>}`
+```
+{result: <response data>}
+```
 
 Response data is generally an object itself, however for some requests this
 may simply be an "ok" string.
@@ -114,1213 +116,1723 @@ The `test/client` folder includes a basic test interface with example usage for
 most of the requests below.  It also includes a basic JSON-RPC implementation
 that uses promises to return responses and errors (see json-rcp.js).
 
-## Printer Administration
+### Printer Administration
 
-### Get Klippy host information:
-- HTTP command:\
-  `GET /printer/info`
+#### Get Klippy host information
 
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "printer.info", id: <request id>}`
+HTTP Request:
+```http
+GET /printer/info
+```
+JSON-RPC Request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "printer.info",
+    "id": 5445
+}
+```
+Returns:
 
-- Returns:\
-  An object containing the build version, cpu info, Klippy's current state.
+An object containing the build version, cpu info, Klippy's current state.
 
-    ```json
-    {
-      state: "<klippy state>",
-      state_message: "<current state message>",
-      hostname: "<hostname>",
-      software_version: "<version>",
-      cpu_info: "<cpu_info>",
-      klipper_path: "<moonraker use only>",
-      python_path: "<moonraker use only>",
-      log_file: "<moonraker use only>",
-      config_file: "<moonraker use only>",
+```json
+{
+    "state": "ready",
+    "state_message": "Printer is ready",
+    "hostname": "my-pi-hostname",
+    "software_version": "v0.9.1-302-g900c7396",
+    "cpu_info": "4 core ARMv7 Processor rev 4 (v7l)",
+    "klipper_path": "/home/pi/klipper",
+    "python_path": "/home/pi/klippy-env/bin/python",
+    "log_file": "/tmp/klippy.log",
+    "config_file": "/home/pi/printer.cfg",
+}
+```
+
+#### Emergency Stop
+HTTP request:
+```http
+POST /printer/emergency_stop
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "printer.emergency_stop",
+    "id": 4564
+}
+```
+Returns:
+
+`ok`
+
+#### Host Restart
+HTTP request:
+```http
+POST /printer/restart
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "printer.restart",
+    "id": 4894
+}
+```
+Returns:
+
+`ok`
+
+#### Firmware Restart
+HTTP request:
+```http
+POST /printer/firmware_restart
+```
+
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "printer.firmware_restart",
+    "id": 8463
+}
+```
+Returns:
+
+`ok`
+
+### Printer Status
+
+#### List available printer objects
+HTTP request:
+```http
+GET /printer/objects/list
+```
+
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "printer.objects.list",
+    "id": 1454
+}
+```
+
+Returns:
+
+An array of "printer objects" that are currently available for query
+or subscription.  This list will be passed in an `objects` parameter.
+
+```json
+{
+    "objects": ["gcode", "toolhead", "bed_mesh", "configfile",...]
+}
+```
+
+#### Query printer object status
+HTTP request:
+```http
+GET /printer/objects/query?gcode_move&toolhead&extruder=target,temperature
+```
+The above will request a status update for all `gcode_move` and `toolhead`
+attributes.  Only the `temperature` and `target` attributes are requested
+for the `extruder`.
+
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "printer.objects.query",
+    "params": {
+        "objects": {
+            "gcode_move": null,
+            "toolhead": ["position", "status"]
+        }
+    },
+    "id": 4654
+}
+```
+**Note: A `null` value will fetch all available attributes for its key.
+
+Returns:
+
+An object where the top level items are "eventtime" and "status".  The
+"status" item contains data about the requested update.
+
+```json
+{
+    "eventtime": 578243.57824499,
+    "status": {
+        "gcode_move": {
+            "absolute_coordinates": true,
+            "absolute_extrude": true,
+            "extrude_factor": 1,
+            "gcode_position": [0, 0, 0, 0],
+            "homing_origin": [0, 0, 0, 0],
+            "position": [0, 0, 0, 0],
+            "speed": 1500,
+            "speed_factor": 1,
+        },
+        "toolhead": {
+            "position": [0, 0, 0, 0],
+            "status": "Ready"
+        }
     }
-    ```
-
-### Emergency Stop
-- HTTP command:\
-  `POST /printer/emergency_stop`
-
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "printer.emergency_stop", id: <request id>}`
-
-- Returns:\
-  `ok`
-
-### Restart the host
-- HTTP command:\
-  `POST /printer/restart`
-
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "printer.restart", id: <request id>}`
-
-- Returns:\
-  `ok`
-
-### Restart the firmware (restarts the host and all connected MCUs)
-- HTTP command:\
-  `POST /printer/firmware_restart`
-
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "printer.firmware_restart", id: <request id>}`
-
-- Returns:\
-  `ok`
-
-## Printer Status
-
-### List available printer objects:
-- HTTP command:\
-  `GET /printer/objects/list`
-
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "printer.objects.list", id: <request id>}`
-
-- Returns:\
-  An a list of "printer objects" that are currently available for query
-  or subscription.  This list will be passed in an "objects" parameter.
-
-  ```json
-  { objects: ["gcode", "toolhead", "bed_mesh", "configfile",....]}
-  ```
-
-### Query printer object status:
-- HTTP command:\
-  `GET /printer/objects/query?gcode`
-
-  The above will fetch a status update for all gcode attributes.  The query
-  string can contain multiple items, and specify individual attributes:
-
-  `?gcode=gcode_position,busy&toolhead&extruder=target`
-
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "printer.objects.query", params:
-    {objects: {gcode: null, toolhead: ["position", "status"]}},
-     id: <request id>}`
-
-  Note that an empty array will fetch all available attributes for its key.
-
-- Returns:\
-  An object where the top level items are "eventtime" and "status".  The
-  "status" item contains data about the requested update.
-
-  ```json
-  {
-    eventtime: <klippy time of update>,
-    status: {
-      gcode: {
-        busy: true,
-        gcode_position: [0, 0, 0 ,0],
-        ...},
-      toolhead: {
-        position: [0, 0, 0, 0],
-        status: "Ready",
-        ...},
-      ...}
-    }
-  ```
+}
+```
 See [printer_objects.md](printer_objects.md) for details on the printer objects
 available for query.
 
-### Subscribe to printer object status:
-- HTTP command:\
-  `POST /printer/objects/subscribe?connection_id=123456789&
-   gcode=gcode_position,bus&extruder=target`
+#### Subscribe to printer object status
+HTTP request:
+```http
+POST /printer/objects/subscribe?connection_id=123456789&gcode_move&extruder`
+```
+**Note:  The HTTP API requires that a `connection_id` is passed via the query
+string or as part of the form.   This should be the
+[ID reported](#get-websocket-id) from a currently connected websocket. A
+request that includes only the `connection_id` argument will cancel the
+subscription on the specified websocket.**
 
-   Note:  The HTTP API requires that a `connection_id` is passed via the query
-   string or as part of the form.   This should be the
-   [ID reported](#get-websocket-id) from a currently connected websocket. A
-   request that includes only the `connection_id` argument will cancel the
-   subscription on the specified websocket.
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "printer.objects.subscribe",
+    "params": {
+        "objects": {
+            "gcode_move": null,
+            "toolhead": ["position", "status"]
+        }
+    },
+    "id": 5434
+}
+```
+**Note: If `objects` is set to an empty object then the subscription will
+be cancelled.**
 
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "printer.objects.subscribe", params:
-    {objects: {gcode: null, toolhead: ["position", "status"]}},
-    id: <request id>}`
+Returns:
 
-    Note that if `objects` is an empty object then the subscription will
-    be cancelled.
+Status data for objects in the request, with the format matching that of
+the `/printer/objects/query`:
 
-- Returns:\
-  Status data for objects in the request, with the format matching that of
-  the `/printer/objects/query`:
-
-  ```json
-  {
-    eventtime: <klippy time of update>,
-    status: {
-      gcode: {
-        busy: true,
-        gcode_position: [0, 0, 0 ,0],
-        ...},
-      toolhead: {
-        position: [0, 0, 0, 0],
-        status: "Ready",
-        ...},
-      ...}
+```json
+{
+    "eventtime": 578243.57824499,
+    "status": {
+        "gcode_move": {
+            "absolute_coordinates": true,
+            "absolute_extrude": true,
+            "extrude_factor": 1,
+            "gcode_position": [0, 0, 0, 0],
+            "homing_origin": [0, 0, 0, 0],
+            "position": [0, 0, 0, 0],
+            "speed": 1500,
+            "speed_factor": 1,
+        },
+        "toolhead": {
+            "position": [0, 0, 0, 0],
+            "status": "Ready"
+        }
     }
-  ```
+}
+```
+
 See [printer_objects.md](printer_objects.md) for details on the printer objects
 available for subscription.
 
 Status updates for subscribed objects are sent asynchronously over the
-websocket.  See the `notify_status_update` notification for details.
-
-### Query Endstops
-- HTTP command:\
-  `GET /printer/query_endstops/status`
-
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "printer.query_endstops.status", id: <request id>}`
-
-- Returns:\
-  An object containing the current endstop state, with each attribute in the
-  format of `endstop:<state>`, where "state" can be "open" or "TRIGGERED", for
-  example:
+websocket.  See the [notify_status_update](#subscriptions)
+notification for details.
 
+#### Query Endstops
+HTTP request:
+```http
+GET /printer/query_endstops/status
+```
+JSON-RPC request:
 ```json
-  {x: "TRIGGERED",
-   y: "open",
-   z: "open"}
+{
+    "jsonrpc": "2.0",
+    "method": "printer.query_endstops.status",
+    "id": 3456
+}
+```
+Returns:
+
+An object containing the current endstop state, where each field is an
+endstop identifier, with a string value of "open" or "TRIGGERED".
+```json
+{
+    "x": "TRIGGERED",
+    "y": "open",
+    "z": "open"
+}
 ```
 
-### Query Server Info
-- HTTP command:\
-  `GET /server/info`
+#### Query Server Info
+HTTP request:
+```http
+GET /server/info
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "server.info",
+    "id": 9546
+}
+```
+Returns:
 
-- Websocket command:
-  `{jsonrpc: "2.0", method: "server.info", id: <request id>}`
-
-- Returns:\
-  An object containing the server's state, structured as follows:
+An object containing various fields that report server state.
 
 ```json
   {
-    klippy_connected: <bool>,
-    klippy_state: <string>,
-    plugins: [<strings>]
+    "klippy_connected": true,
+    "klippy_state": "ready",
+    "plugins": [
+        "database",
+        "file_manager",
+        "klippy_apis",
+        "machine",
+        "data_store",
+        "shell_command",
+        "proc_stats",
+        "history",
+        "octoprint_compat",
+        "update_manager",
+        "power"
+    ],
+    "failed_plugins": [],
+    "registered_directories": ["config", "gcodes", "config_examples", "docs"]
   }
 ```
-  Note that `klippy_state` will match the `state` value received from
-  `/printer/info`. The `klippy_connected` item tracks the state of the
-  connection to Klippy. The `plugins` key will return a list of all
-  enabled plugins.  This can be used by clients to check if an optional
-  plugin is available.
+Note that `klippy_state` will match the `state` value received from
+`/printer/info`. The `klippy_connected` item tracks the state of the
+unix domain socket connect to Klippy. The `plugins` key will return a list of
+enabled plugins.  This can be used by clients to check if an optional
+plugin is available.  Optional plugins that do not load correctly will not
+prevent the server from starting, thus any plugins that failed to load will be
+reported in the `failed_plugins` field.
 
-### Get Server Configuration
-- HTTP command:\
-  `GET /server/config`
+#### Get Server Configuration
+HTTP request:
+```http
+GET /server/config
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "server.config",
+    "id": 5616,
+}
+```
+Returns:
 
-- Websocket command:
-  `{jsonrpc: "2.0", method: "server.config", id: <request id>}`
-
-- Returns:\
-  An object containing the server's configuration, structured as follows:
+An object containing the server's full configuration.  Note that
+this includes auxiliary configuration sections not part of `moonraker.conf`,
+for example the `update_manager static debian moonraker` section.
+Options not specified in `moonraker.conf` with default values are also
+included.
 
 ```json
 {
-    config: {
-        server: {
-            ...
+    "config": {
+        "server": {
+            "host": "0.0.0.0",
+            "port": 7125,
+            "klippy_uds_address": "/tmp/klippy_uds",
+            "max_upload_size": 210,
+            "enable_debug_logging": true,
+            "database_path": "~/.moonraker_database",
+            "config_path": "~/printer_config",
+            "temperature_store_size": 100,
+            "gcode_store_size": 50
         },
-        authorization: {
-            ...
+        "authorization": {
+            "api_key_file": "~/.moonraker_api_key",
+            "enabled": true,
+            "cors_domains": "\nhttp://my.mainsail.xyz\nhttp://app.fluidd.xyz",
+            "trusted_clients": "\n192.168.1.0/24"
         },
-        ...
+        "system_args": {},
+        "history": {},
+        "octoprint_compat": {},
+        "update_manager": {
+            "enable_auto_refresh": true,
+            "distro": "debian",
+            "enable_repo_debug": true,
+            "client_repo": null
+        },
+        "update_manager static debian moonraker": {},
+        "update_manager client mainsail": {
+            "type": "web",
+            "repo": "meteyou/mainsail",
+            "path": "~/mainsail",
+            "persistent_files": null
+        },
+        "update_manager client fluidd": {
+            "type": "web",
+            "repo": "cadriel/fluidd",
+            "path": "~/fluidd",
+            "persistent_files": null
+        },
+        "power green_led": {
+            "type": "gpio",
+            "locked_while_printing": false,
+            "off_when_shutdown": false,
+            "restart_klipper_when_powered": false,
+            "pin": "gpiochip0/gpio26",
+            "initial_state": false
+        },
+        "update_manager static debian klipper": {}
+    }
+}
+```
+#### Request Cached Temperature Data
+HTTP request:
+```http
+GET /server/temperature_store
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "server.temperature_store",
+    "id": 2313
+}
+```
+Returns:
+
+An object where the keys are the available temperature sensor names, and with
+the value being an array of stored temperatures.  The array is updated every
+1 second by default, containing a total of 1200 values (20 minutes).  The
+array is organized from oldest temperature to most recent (left to right).
+Note that when the host starts each array is initialized to 0s.
+```json
+{
+    "extruder": {
+        "temperatures": [21.05, 21.12, 21.1, 21.1, 21.1],
+        "targets": [0, 0, 0, 0, 0],
+        "powers": [0, 0, 0, 0, 0]
+    },
+    "temperature_fan my_fan": {
+        "temperatures": [21.05, 21.12, 21.1, 21.1, 21.1],
+        "targets": [0, 0, 0, 0, 0],
+        "speeds": [0, 0, 0, 0, 0],
+    },
+    "temperature_sensor my_sensor": {
+        "temperatures": [21.05, 21.12, 21.1, 21.1, 21.1]
     }
 }
 ```
 
-### Fetch stored temperature data
-- HTTP command:\
-  `GET /server/temperature_store`
-
-- Websocket command:
-  `{jsonrpc: "2.0", method: "server.temperature_store", id: <request id>}`
-
-- Returns:\
-  An object where the keys are the available temperature sensor names, and with
-  the value being an array of stored temperatures.  The array is updated every
-  1 second by default, containing a total of 1200 values (20 minutes).  The
-  array is organized from oldest temperature to most recent (left to right).
-  Note that when the host starts each array is initialized to 0s.
-  ```json
-  {
-      extruder: {
-          temperatures: [],
-          targets: [],
-          powers: []
-      },
-      temperature_fan my_fan: {
-          temperatures: [],
-          targets: [],
-          speeds: [],
-      },
-      temperature_sensor my_sensor: {
-          temperatures: []
-      }
-  }
-  ```
-
-### Fetch stored gcode info
-- HTTP command:\
-  `GET /server/gcode_store`
-
-  Optionally, a `count` argument may be added to specify the number of
-  responses to fetch. If omitted, the entire gcode store will be sent
-  (up to 1000 responses).
-
-  `GET /server/gcode_store?count=100`
-
-- Websocket command:
-  `{jsonrpc: "2.0", method: "server.gcode_store", id: <request id>}`
-
-  OR
-  `{jsonrpc: "2.0", method: "server.gcode_store",
-   params: {count: <integer>} id: <request id>}`
-
-- Returns:\
-  An object with the field `gcode_store` that contains an array
-  of objects.  Each object will contain a `message` field and a
-  `time` field:
-```json
-  {
-    gcode_store: [
-      {
-        message: <string>,
-        time: unix_time_stamp,
-        type: <string>
-      }, ...
-    ]
-  }
+#### Request Cached GCode Responses
+HTTP request:
+```http
+GET /server/gcode_store?count=100
 ```
-Each `message` field contains a gcode response received at the time
-indicated in the `time` field. Note that the time stamp refers to
-unix time (in seconds).  This can be used to create a JavaScript
-`Date` object:
-```javascript
-for (let resp of result.gcode_store) {
-  let date = new Date(resp.time * 1000);
-  // Do something with date and resp.message ...
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "server.gcode_store",
+    "params": {
+        "count": 100
+    },
+    "id": 7643}
+```
+
+The `count` argument is optional, limiting number of returned items
+in the response to the value specified. If omitted, the entire gcode
+store will be returned (up to 1000 responses).
+
+Returns:
+
+An object with the field `gcode_store` that contains an array
+of objects.  Each object will contain `message`, `time`, and
+`type` fields.  The `time` field is reported in Unix Time.
+The `type` field will either be `command` or `response`.
+```json
+{
+    "gcode_store": [
+        {
+            "message": "FIRMWARE_RESTART",
+            "time": 1615832299.1167388,
+            "type": "command"
+        },
+        {
+            "message": "// Klipper state: Ready",
+            "time": 1615832309.9977088,
+            "type": "response"
+        },
+        {
+            "message": "M117 This is a test",
+            "time": 1615834094.8662775,
+            "type": "command"
+        },
+        {
+            "message": "G4 P1000",
+            "time": 1615834098.761729,
+            "type": "command"
+        },
+        {
+            "message": "STATUS",
+            "time": 1615834104.2860553,
+            "type": "command"
+        },
+        {
+            "message": "// Klipper state: Ready",
+            "time": 1615834104.3299904,
+            "type": "response"
+        }
+    ]
 }
 ```
-The `type` field will either be "command" or "response".
-
-### Restart Server
-- HTTP command:\
-  `POST /server/restart`
-
-- Websocket command:
-  `{jsonrpc: "2.0", method: "server.restart", id: <request id>}`
-
-- Returns:\
-  `"ok"` upon receipt of the restart request.  After the request
-  is returns, the server will restart.  Any existing connection
-  will be disconnected.  A restart will result in the creation
-  of a new server instance where the configuration is reloaded.
-
-## Get Websocket ID
-- HTTP command:\
-  Not Available
-
-- Websocket command:
-  `{jsonrpc: "2.0", method: "server.websocket.id", id: <request id>}`
-
-- Returns:\
-  This connected websocket's unique identifer in the format shown below.
-  Note that this API call is only available over the websocket.
 
+#### Restart Server
+HTTP request:
+```http
+POST /server/restart
+```
+JSON-RPC request:
 ```json
-  {
-    websocket_id: <int>
-  }
+{
+    "jsonrpc": "2.0",
+    "method": "server.restart",
+    "id": 4656
+}
+```
+Returns:
+
+`ok` upon receipt of the restart request.  After the request
+is returns, the server will restart.  Any existing connection
+will be disconnected.  A restart will result in the creation
+of a new server instance where the configuration is reloaded.
+
+#### Get Websocket ID
+HTTP request: `Not Available`
+
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "server.websocket.id",
+    "id": 4656
+}
+```
+Returns:
+
+The connected websocket's unique identifer.
+```json
+{
+    "websocket_id": 1730367696
+}
 ```
 
-## Gcode Controls
+### GCode APIs
 
-### Run a gcode:
-- HTTP command:\
-  `POST /printer/gcode/script?script=<gc>`
+#### Run a gcode:
+HTTP request:
+```http
+POST /printer/gcode/script?script=G28
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "printer.gcode.script",
+    "params": {
+        "script": "G28"
+    },
+    "id": 7466}
+```
 
-  For example,\
-  `POST /printer/gcode/script?script=RESPOND MSG=Hello`\
-  Will echo "Hello" to the terminal.
+Returns:
 
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "printer.gcode.script",
-    params: {script: <gc>}, id: <request id>}`
+`ok` when the gcode has completed execution.
+#### Get GCode Help
+HTTP request:
+```http
+GET /printer/gcode/help
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "printer.gcode.help",
+    "id": 4645
+}
+```
 
-- Returns:\
-  An acknowledgement that the gcode has completed execution:
+Returns:
 
-  `ok`
+An object where they keys are gcode handlers and values are the associated
+help strings.  Note that help strings are not available for default gcode
+handlers such as G1, G28, etc, nor are they available for extended handlers
+that failed to register a description in Klippy.
+```json
+{
+    "RESTORE_GCODE_STATE": "Restore a previously saved G-Code state",
+    "PID_CALIBRATE": "Run PID calibration test",
+    "QUERY_ADC": "Report the last value of an analog pin",
+    "TUNING_TOWER": "Tool to adjust a parameter at each Z height",
+    "SAVE_CONFIG": "Overwrite config file and restart",
+    "SET_DISPLAY_GROUP": "Set the active display group",
+    "SAVE_GCODE_STATE": "Save G-Code coordinate state",
+    "SET_PRESSURE_ADVANCE": "Set pressure advance parameters",
+    "SET_GCODE_OFFSET": "Set a virtual offset to g-code positions",
+    "BED_TILT_CALIBRATE": "Bed tilt calibration script",
+    ...
+}
+```
 
-### Get GCode Help
-- HTTP command:\
-  `GET /printer/gcode/help`
+### Print Management
 
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "printer.gcode.help",
-    params: {script: <gc>}, id: <request id>}`
+#### Print a file
+HTTP request:
+```http
+POST /printer/print/start?filename=test_print.gcode
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "printer.print.start",
+    "params": {
+        "filename": "test_pring.gcode"
+    },
+    "id": 4654
+}
+```
+Returns:
 
-- Returns:\
-  An object where they keys are gcode handlers and values are the associated
-  help strings.  Note that help strings are not available for basic gcode
-  handlers such as G1, G28, etc.
+`ok`
 
-## Print Management
+#### Pause a print
+HTTP request:
+```http
+POST /printer/print/pause
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "printer.print.pause",
+    "id": 4564
+}
+```
+Returns:
 
-### Print a file
-- HTTP command:\
-  `POST /printer/print/start?filename=<file name>`
+`ok`
 
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "printer.print.start",
-    params: {filename: <file name>, id:<request id>}`
+#### Resume a print
+HTTP request:
+```http
+POST /printer/print/resume
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "printer.print.resume",
+    "id": 1465
+}
+```
+Returns:
 
-- Returns:\
-  `ok` on success
+`ok`
 
-### Pause a print
-- HTTP command:\
-  `POST /printer/print/pause`
+#### Cancel a print
+HTTP request:
+```http
+POST /printer/print/cancel
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "printer.print.cancel",
+    "id": 2578
+}
+```
+Returns:
 
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "printer.print.pause", id: <request id>}`
+`ok`
 
-- Returns:\
-  `ok`
+### Machine Commands
 
-### Resume a print
-- HTTP command:\
-  `POST /printer/print/resume`
+#### Shutdown the Operating System
+HTTP request:
+```http
+POST /machine/shutdown
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "machine.shutdown",
+    "id": 4665
+}
+```
+Returns:
 
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "printer.print.resume", id: <request id>}`
+This request will not return.  The machine will shutdown
+and the socket connection will drop.
 
-- Returns:\
-  `ok`
+#### Reboot the Operating System
+HTTP request:
+```http
+POST /machine/reboot
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "machine.reboot",
+    "id": 4665
+}
+```
+Returns:
 
-### Cancel a print
-- HTTP command:\
-  `POST /printer/print/cancel`
+This request will not return.  The machine will reboot
+and the socket connection will drop.
 
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "printer.print.cancel", id: <request id>}`
+#### Restart a system service
+Restarts a system service via `sudo systemctl restart {name}`. Currently
+only the `moonraker`, `klipper`, and `webcamd` services are supported.
 
-- Returns:\
-  `ok`
+HTTP request:
+```http
+POST /machine/services/restart?service={name}
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "machine.services.restart",
+    "params": {
+        "service": "{name}"
+    },
+    "id": 4656}
+```
 
-## Machine Commands
+Returns:
 
-### Shutdown the Operating System
-- HTTP command:\
-  `POST /machine/shutdown`
+`ok` when complete.  Note that if `moonraker` is chosen, the return
+value will be sent prior to the service restart.
 
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "machine.shutdown", id: <request id>}`
-
-- Returns:\
-  No return value as the server will shut down upon execution
-
-### Reboot the Operating System
-- HTTP command:\
-  `POST /machine/reboot`
-
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "machine.reboot", id: <request id>}`
-
-- Returns:\
-  No return value as the server will shut down upon execution
-
-### Restart a system service
-Restarts a system service via `sudo systemctl restart <name>`. Currently
-only `moonraker`, `klipper`, and `webcamd` are allowed.
-
-- HTTP command:\
-  `POST /machine/services/restart?service=<service_name>`
-
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "machine.services.restart",
-  params: {service: "service name"}, id: <request id>}`
-
-- Returns:\
-  `ok` when complete.  Note that if `moonraker` is chosen, the return
-  value will be sent prior to the restart.
-
-### Stop a system service
+#### Stop a system service
 Stops a system service via `sudo systemctl stop <name>`. Currently
-only `webcamd` and `klipper` are allowed.
+only `webcamd` and `klipper` are supported.
 
-- HTTP command:\
-  `POST /machine/services/stop?service=<service_name>`
+HTTP request:
+```http
+POST /machine/services/stop?service={name}
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "machine.services.stop",
+    "params": {
+        "service": "{name}"
+    },
+    "id": 4645
+}
+```
 
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "machine.services.stop",
-  params: {service: "service name"}, id: <request id>}`
+Returns:
 
-- Returns:\
-  `ok` when complete
+`ok`
 
-### Start a system service
+#### Start a system service
 Starts a system service via `sudo systemctl start <name>`. Currently
-only `webcamd` and `klipper` are allowed.
+only `webcamd` and `klipper` are supported.
 
-- HTTP command:\
-  `POST /machine/services/start?service=<service_name>`
+HTTP request:
+```http
+POST /machine/services/start?service={name}
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "machine.services.start",
+    "params": {
+        "service": "{name}"
+    },
+    "id": 4645
+}
+```
 
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "machine.services.start",
-  params: {service: "service name"}, id: <request id>}`
+Returns:
 
-- Returns:\
-  `ok` when complete
+`ok`
 
-### Get Process Info
+#### Get Moonraker Process Stats
 Returns system usage information about the moonraker process.
 
-- HTTP command:\
-  `GET /machine/proc_info`
+HTTP request:
+```http
+GET /machine/proc_stats
+```
 
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "machine.proc_info", id: <request id>}`
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "machine.proc_stats",
+    "id": 7896
+}
+```
+Returns:
 
-- Returns:\
-  An object in the following format:
-  ```json
-  {
-      proc_info: [
-          {
-              time: <system time of sample>,
-              cpu_usage: <usage percent>,
-              memory: <memory_used>,
-              mem_units: "<kB, mB, etc>"
-          },
-          ...
-      ],
-      throttled_state: {
-          bits: <throttled bits>,
-          flags: ["flag1", "flag2", ...]
-      }
-  }
-  ```
-  Process information is sampled every second.  The `proc_info` field
-  will return up to 30 samples, each sample with the following fields:
-  - `time`: Time of the sample (in seconds since the Epoch)
-  - `cpu_usage`: A floating point value between 0-100, representing the
-    CPU usage of the Moonraker process.
-  - `memory`: Integer value representing the current amount of memory
-    allocated in RAM (resident set size).
-  - `mem_units`: A string indentifying the units of the value in the
-    `memory` field.  This is typically "kB", but not guaranteed.
-  If the system running Moonraker supports `vcgencmd` then Moonraker
-  will check the current throttled flags via `vcgencmd get_throttled`
-  and report them in the `throttled_state` field:
-  - `bits`: An integer value that represents the bits reported by
-    `vcgencmd get_throttled`
-  - `flags`: Descriptive flags parsed out of the bits.  One or more
-    of the following flags may be reported:
-    - "Under-Voltage Detected"
-    - "Frequency Capped"
-    - "Currently Throttled"
-    - "Temperature Limit Active"
-    - "Previously Under-Volted"
-    - "Previously Frequency Capped"
-    - "Previously Throttled"
-    - "Previously Temperature Limited"
-  The first four flags indicate an active throttling condition,
-  whereas the last four indicate a previous condition (may or
-  may not still be active).  If `vcgencmd` is not available the
-  `throttled_state` will report `null`.
+An object in the following format:
+```json
+{
+    "moonraker_stats": [
+        {
+            "time": 1615837812.0894408,
+            "cpu_usage": 1.99,
+            "memory": 23636,
+            "mem_units": "kB"
+        },
+        {
+            "time": 1615837813.0890627,
+            "cpu_usage": 2.09,
+            "memory": 23636,
+            "mem_units": "kB"
+        },
+        ...
+    ],
+    "throttled_state": {
+        "bits": 0,
+        "flags": []
+    }
+}
+```
+Process information is sampled every second.  The `moonraker_stats` field
+will return up to 30 samples, each sample with the following fields:
 
-## File Operations
+- `time`: Time of the sample (in seconds since the Epoch)
+- `cpu_usage`: A floating point value between 0-100, representing the
+CPU usage of the Moonraker process.
+- `memory`: Integer value representing the current amount of memory
+allocated in RAM (resident set size).
+- `mem_units`: A string indentifying the units of the value in the
+`memory` field.  This is typically "kB", but not guaranteed.
 
-Most file operations are available over both APIs, however file upload,
-file download, and file delete are currently only available via HTTP APIs.
+If the system running Moonraker supports `vcgencmd` then Moonraker
+will check the current throttled flags via `vcgencmd get_throttled`
+and report them in the `throttled_state` field:
 
-Moonraker organizes different local directories into "roots".  For example,
+- `bits`: An integer value that represents the bits reported by
+`vcgencmd get_throttled`
+- `flags`: Descriptive flags parsed out of the bits.  One or more
+of the following flags may be reported:
+- "Under-Voltage Detected"
+- "Frequency Capped"
+- "Currently Throttled"
+- "Temperature Limit Active"
+- "Previously Under-Volted"
+- "Previously Frequency Capped"
+- "Previously Throttled"
+- "Previously Temperature Limited"
+
+The first four flags indicate an active throttling condition,
+whereas the last four indicate a previous condition (may or
+may not still be active).  If `vcgencmd` is not available
+`throttled_state` will report `null`.
+
+### File Operations
+
+Most file operations are available over both APIs, however file upload and
+file download are currently only available via HTTP APIs.
+
+Moonraker organizes local directories into "roots".  For example,
 gcodes are located at `http:\\host\server\files\gcodes\*`, otherwise known
 as the "gcodes" root.  The following roots are available:
+
 - gcodes
 - config
 - config_examples (read-only)
 - docs (read-only)
 
 Write operations (upload, delete, make directory, remove directory) are
-only available on the `gcodes` and config roots.  Note that the `config` root
-is only available if the "config_path" option has been set in Moonraker's
+only available on the `gcodes` and `config` roots.  Note that the `config` root
+is only available if the `config_path` option has been set in Moonraker's
 configuration.
 
-### List Available Files
+#### List available files
 Walks through a directory and fetches all files.  All file names include a
-path relative to the specified "root".  Note that if the query st
+path relative to the specified `root`.
 
-- HTTP command:\
-  `GET /server/files/list?root=gcodes`
+HTTP request:
+```http
+GET /server/files/list?root={root_folder}
+```
 
-  If the query string is omitted then the command will return
-  the "gcodes" file list by default.
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "server.files.list",
+    "params": {
+        "root": "{root_folder}"
+    },
+    "id": 4644
+}
+```
 
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "server.files.list", params: {root: "gcodes"}
-  , id: <request id>}`
+**Note: If the `root` argument is omitted the request will default to
+the `gcodes` root.**
 
-  If `params` are are omitted then the command will return the "gcodes"
-  file list.
-
-- Returns:\
-  A list of objects containing file data in the following format:
+**Note: The `gcodes` root will only return files with valid gcode
+extensions.**
 
+Returns:
+A list of objects, where each object contains file data.
 ```json
 [
-  {filename: "file name",
-   size: <file_size>,
-   modified: <unix_time>,
-   ...]
+    {
+        "filename": "3DBenchy_0.15mm_PLA_MK3S_2h6m.gcode",
+        "modified": 1615077020.2025201,
+        "size": 4926481
+    },
+    {
+        "filename": "Shape-Box_0.2mm_PLA_Ender2_20m.gcode",
+        "modified": 1614910966.946807,
+        "size": 324236
+    },
+    {
+        "filename": "test_dir/A-Wing.gcode",
+        "modified": 1605202259,
+        "size": 1687387
+    },
+    {
+        "filename": "test_dir/CE2_CubeTest.gcode",
+        "modified": 1614644445.4025,
+        "size": 1467339
+    },
+    {
+        "filename": "test_dir/V350_Engine_Block_-_2_-_Scaled.gcode",
+        "modified": 1615768477.5133543,
+        "size": 189713016
+    },
+]
 ```
 
-### Get GCode Metadata
-  Get file metadata for a specified gcode file.  If the file is located in
-  a subdirectory, then the file name should include the path relative to
-  the "gcodes" root.  For example, if the file is located at:\
-  `http://host/server/files/gcodes/my_sub_dir/my_print.gcode`
-  Then the filename should be `my_sub_dir/my_print.gcode`.
+#### Get gcode metadata
+Get metadata for a specified gcode file.  If the file is located in
+a subdirectory, then the file name should include the path relative to
+the "gcodes" root.  For example, if the file is located at:
+```
+http://host.local/server/files/gcodes/my_sub_dir/my_print.gcode
+```
+Then the `{filename}` argument should be `my_sub_dir/my_print.gcode`.
 
-- HTTP command:\
-  `GET /server/files/metadata?filename=<filename>`
+HTTP request:
+```http
+GET /server/files/metadata?filename={filename}
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "server.files.metadata",
+    "params": {
+        "filename": "{filename}"
+    },
+    "id": 3545
+}
+```
 
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "server.files.metadata", params: {filename: "filename"}
-  , id: <request id>}`
+Returns:
 
-- Returns:\
-  Metadata for the requested file if it exists.  If any fields failed
-  parsing they will be omitted.  The metadata will always include the file name,
-  modified time, and size.
+Metadata for the requested file if it exists.  If any fields failed
+parsing they will be omitted.  The metadata will always include the file name,
+modified time, and size.
 
 ```json
-  {
-    filename: "file name",
-    size: <file_size>,
-    modified: <unix_time>,
-    slicer: "Slicer Name",
-    slicer_version: "<version>",
-    first_layer_height: <mm>,
-    first_layer_bed_temp: <C>,
-    first_layer_extr_temp: <C>,
-    layer_height: <mm>,
-    object_height: <mm>,
-    estimated_time: <time_in_seconds>,
-    filament_total: <mm>,
-    gcode_start_byte: <byte_location_of_first_gcode_command>,
-    gcode_end_byte: <byte_location_of_last_gcode_command>,
-    thumbnails: [
-      {
-        width: <in_pixels>,
-        height: <in_pixels>,
-        size: <length_of_string>,
-        relative_path: <relative_path_to_png>
-      }, ...
-    ]
-  }
+{
+    "size": 4926481,
+    "modified": 1615077020.2025201,
+    "slicer": "SuperSlicer",
+    "slicer_version": "2.2.52",
+    "layer_height": 0.15,
+    "first_layer_height": 0.2,
+    "object_height": 48.05,
+    "filament_total": 4056.4,
+    "estimated_time": 7569,
+    "thumbnails": [
+        {
+            "width": 32,
+            "height": 32,
+            "size": 2596,
+            "data": "{base64_data}"
+            "relative_path": "thumbs/3DBenchy_0.15mm_PLA_MK3S_2h6m-32x32.png"
+        },
+        {
+            "width": 400,
+            "height": 300,
+            "size": 73308,
+            "data": "{base64_data}",
+            "relative_path": "thumbs/3DBenchy_0.15mm_PLA_MK3S_2h6m-400x300.png"
+        }
+    ],
+    "first_layer_bed_temp": 60,
+    "first_layer_extr_temp": 215,
+    "gcode_start_byte": 79451,
+    "gcode_end_byte": 4915668,
+    "filename": "3DBenchy_0.15mm_PLA_MK3S_2h6m.gcode"
+}
 ```
+**Note: The `data` field for each thumbnail is deprecated and will be removed
+in a future release.  Clients should retrieve the png directly using the
+`relative_path` field.**
 
-### Get directory information
+#### Get directory information
 Returns a list of files and subdirectories given a supplied path.
 Unlike `/server/files/list`, this command does not walk through
-subdirectories.
-
-- HTTP command:\
-  `GET /server/files/directory?path=gcodes/my_subdir&extended=true`
-
-  If the query string is omitted then the command will return
-  the "gcodes" file list by default.
-
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "server.files.get_directory",
-   params: {path: "gcodes/my_subdir", extended: true} ,
-   id: <request id>}`
-
-  If the "params" are omitted then the command will return
-  the "gcodes" file list by default.
-
-The `extended` argument is optional, and defaults to false. If
-specified and set to true, then data returned for gcode files
-will also include metadata if it is available.
-
-- Returns:\
-  An object containing file and subdirectory information in the
-  following format:
+subdirectories.  This request will return all files in a directory,
+including files in the `gcodes` root that do not have a valid gcode
+extension.
 
+HTTP request:
+```http
+GET /server/files/directory?path=gcodes/my_subdir&extended=true
+```
+JSON-RPC request:
 ```json
-  {
-    files: [
-      {
-        filename: "file name",
-        size: <file_size>,
-        modified: <unix_time>
-      }, ...
-    ],
-    dirs: [
-      {
-        dirname: "directory name",
-        modified: <unix_time>
-      }
-    ]
-  }
+{
+    "jsonrpc": "2.0",
+    "method": "server.files.get_directory",
+    "params": {
+        "path": "gcodes/my_subdir",
+        "extended": true
+    },
+    "id": 5644
+}
 ```
 
-### Make new directory
-Creates a new directory at the specified path.
+**Note: If the `path` argument is omitted then the command will return
+directory information from the `gcodes` root.**
 
-- HTTP command:\
-  `POST /server/files/directory?path=gcodes/my_new_dir`
+The `extended` argument is optional and defaults to false. If
+supplied and set to true then data returned for gcode files
+will also include metadata (if available).
 
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "server.files.post_directory", params:
-   {path: "gcodes/my_new_dir"}, id: <request id>}`
+Returns:
 
-Returns:\
-`ok` if successful
+An object containing file and subdirectory information in the
+following format:
 
-### Delete directory
+```json
+{
+    "dirs": [
+        {
+            "modified": 1615768162.0412788,
+            "size": 4096,
+            "dirname": "test"
+        },
+        {
+            "modified": 1613569827.489749,
+            "size": 4096,
+            "dirname": "Cura"
+        },
+        {
+            "modified": 1615767459.6265886,
+            "size": 4096,
+            "dirname": "thumbs"
+        }
+    ],
+    "files": [
+        {
+            "modified": 1615578004.9639666,
+            "size": 7300692,
+            "filename": "Funnel_0.2mm_PLA_Ender2_2h4m.gcode"
+        },
+        {
+            "modified": 1589156863.9726968,
+            "size": 4214831,
+            "filename": "CE2_Pi3_A+_CaseLID.gcode"
+        },
+        {
+            "modified": 1615030592.7722695,
+            "size": 2388774,
+            "filename": "CE2_calicat.gcode"
+        },
+    ],
+    "disk_usage": {
+        "total": 7522213888,
+        "used": 4280369152,
+        "free": 2903625728
+    }
+}
+```
+
+#### Create directory
+Creates a directory at the specified path.
+
+HTTP request:
+```http
+POST /server/files/directory?path=gcodes/my_new_dir
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "server.files.post_directory",
+    "params": {
+        "path": "gcodes/my_new_dir"
+    },
+    "id": 6548
+}
+```
+
+Returns:
+
+`ok`
+
+#### Delete directory
 Deletes a directory at the specified path.
 
-- HTTP command:\
-  `DELETE /server/files/directory?path=gcodes/my_subdir`
+HTTP request:
+```http
+DELETE /server/files/directory?path=gcodes/my_subdir&force=false
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "server.files.delete_directory",
+    "params": {
+        "path": "gcodes/my_new_dir",
+        "force": false
+    },
+    "id": 6545
+}
+```
+**Note: If the specified directory contains files then the delete request
+will fail unless the `force` argument is set to `true`.**
 
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "server.files.delete_directory", params:
-   {path: "gcodes/my_subdir"} , id: <request id>}`
+Returns:
 
-  If the specified directory contains files then the delete request
-  will fail, however it is possible to "force" deletion of the directory
-  and all files in it with and additional argument in the query string:\
-  `DELETE /server/files/directory?path=gcodes/my_subdir&force=true`
+`ok`
 
-  OR to the JSON-RPC params:\
-  `{jsonrpc: "2.0", method: "get_directory", params:
-   {path: "gcodes/my_subdir", force: True}, id: <request id>}`
-
-  Note that a forced deletion will still check in with Klippy to be sure
-  that a file in the requested directory is not loaded by the virtual_sdcard.
-
-- Returns:\
-`ok` if successful
-
-### Move a file or directory
-Moves a file or directory from one location to another. Note that the following
+#### Move a file or directory
+Moves a file or directory from one location to another. The following
 conditions must be met for a move successful move:
-- The source must exist
-- The source and destinations must have the same "root" directory
-- The user (typically "Pi") must have the appropriate file permissions
-- Neither the source nor destination can be loaded by the virtual_sdcard.
-  If the source or destination is a directory, it cannot contain a file
-  loaded by the virtual_sdcard.
 
-When specifying the `source` and `dest`, the "root" directory should be
-prefixed. Currently the only supported roots are "gcodes/" and "config/".
+- The source must exist
+- The user (typically "pi") must have the appropriate file permissions
+- Neither the source nor destination can be loaded by the `virtual_sdcard`.
+  If the source is a directory, it must not contain a file loaded by the
+  `virtual_sdcard`.
+
+When specifying the `source` and `dest`, the `root` directory should be
+prefixed. Currently the only supported roots for `dest` are `gcodes`"
+and `config`".
 
 This API may also be used to rename a file or directory.   Be aware that an
 attempt to rename a directory to a directory that already exists will result
-in *moving* the source directory to the destination directory.
+in *moving* the source directory into the destination directory.
 
-- HTTP command:\
-  `POST /server/files/move?source=gcodes/my_file.gcode
-  &dest=gcodes/subdir/my_file.gcode`
+HTTP request:
+```http
+POST /server/files/move?source=gcodes/my_file.gcode&dest=gcodes/subdir/my_file.gcode
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "server.files.move",
+    "params": {
+        "source": "gcodes/my_file.gcode",
+        "dest": "gcodes/subdir/my_file.gcode"
+    },
+    "id": 5664
+}
+```
 
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "server.files.move", params:
-   {source: "gcodes/my_file.gcode",
-   dest: "gcodes/subdir/my_file.gcode"}, id: <request id>}`
+Returns:
 
-### Copy a file or directory
+`ok`
+
+#### Copy a file or directory
 Copies a file or directory from one location to another.  A successful copy has
 the pre-requesites as a move with one exception, a copy may complete if the
-source file/directory is loaded by the virtual_sdcard.  As with the move API,
-the source and destination should have the root prefixed.
+source file or directory is loaded by the `virtual_sdcard`.  As with the move
+API, the `source` and `dest` should have the root prefixed to the path.
 
-- HTTP command:\
-  `POST /server/files/copy?source=gcodes/my_file.gcode
-   &dest=gcodes/subdir/my_file.gcode`
+HTTP request:
+```http
+POST /server/files/copy?source=gcodes/my_file.gcode&dest=gcodes/subdir/my_file.gcode
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "server.files.copy",
+    "params": {
+        "source": "gcodes/my_file.gcode",
+        "dest": "gcodes/subdir/my_file.gcode"
+    },
+    "id": 5623
+}
+```
 
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "server.files.copy", params:
-   {source: "gcodes/my_file.gcode", dest: "gcodes/subdir/my_file.gcode"},
-   id: <request id>}`
+Returns:
 
-### Gcode File Download
-- HTTP command:\
-  `GET /server/files/gcodes/<file_name>`
+`ok`
 
-- Websocket command:\
-  Not Available
+#### File download
+Retreives file `filename` at root `root`.  The `filename` must include
+the relative path if it is not in the root folder.
 
-- Returns:\
-  The requested file
+HTTP request:
+```http
+GET /server/files/{root}/{filename}
+```
+JSON-RPC request: Not Available
 
-### File Upload
-Upload a file.  Currently files may be uploaded to the "gcodes" or "config"
-root, with "gcodes" being the default location.  If one wishes to upload
+Returns:
+
+The requested file
+
+#### File upload
+Upload a file.  Currently files may be uploaded to the `gcodes` or `config`
+roots, with `gcodes` being the default.  If one wishes to upload
 to a subdirectory, the path may be added to the upload's file name
 (relative to the root). If the directory does not exist an error will be
-returned.  Alternatively, the "path" argument may be set, as explained
+returned.  Alternatively, the `path` form argument may be set, as explained
 below.
 
-- HTTP command:\
-  `POST /server/files/upload`
+HTTP request:
+```http
+POST /server/files/upload`
+```
 
-  The file to be uploaded should be added to the FormData per the XHR spec.
-  The following arguments may be added to the form:
-  - root: The root location in which to upload the file.  Currently this may
-    be "gcodes" or "config".  If not specified the default is "gcodes".
-  - path: This argument may contain a path (relative to the root) indicating
-    a subdirectory to which the file is written. If a "path" is present, the
-    server will attempt to create any subdirectories that do not exist.
-  Arguments available only for the "gcodes" root:
-  - print: If set to "true", Klippy will attempt to start the print after
-    uploading.  Note that this value should be a string type, not boolean. This
-    provides compatibility with Octoprint's legacy upload API.
+The file must be uploaded in the request's body `multipart/form-data` (ie:
+`<input type="file">`).  The following fields may also be added to the form:
 
-- Websocket command:\
-  Not Available
+- `root`: The root location in which to upload the file.  Currently this may
+be `gcodes` or `config`.  If not specified the default is `gcodes`.
+- `path`: This argument may contain a path (relative to the root) indicating
+a subdirectory to which the file is written. If a `path` is present the
+server will attempt to create any subdirectories that do not exist.
 
-- Returns:\
-  The file name along with a successful response.
-  ```json
-  {'result': "file_name"}
-  ```
-  If the supplied root is "gcodes", a "print_started" attribute is also
-   returned.
-  ```json
-  {'result': "file_name", 'print_started': <boolean>}
-  ```
+Arguments available only for the `gcodes` root:
 
-### Gcode File Delete
-Delete a file in the "gcodes" root.  A relative path may be added to the file
-to delete a file in a subdirectory.
-- HTTP command:\
-  `DELETE /server/files/gcodes/<file_name>`
+- `print`: If set to "true", Klippy will attempt to start the print after
+uploading.  Note that this value should be a string type, not boolean. This
+provides compatibility with Octoprint's legacy upload API.
 
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "server.files.delete_file", params:
-   {path: "gcodes/<file_name>"}, id: <request id>}`
+JSON-RPC request: Not Available
 
-   If the gcode file exists within a subdirectory, the relative
-   path should be included in the file name.
+Returns:
 
-- Returns:\
-  The HTTP request returns the name of the deleted file.
+The name of the uploaded file.
+```json
+{
+    "result": "{file_name}"
+}
+```
 
-### Download included config file
-- HTTP command:\
-  `GET /server/files/config/<file_name>`
+If the supplied root is "gcodes", a "print_started" field is also
+returned.
+```json
+{
+    "result": "{file_name}",
+    "print_started": false
+}
+```
 
-- Websocket command:\
-  Not Available
+#### File delete
+Delete a file in the requested root.  If the file exists in a subdirectory,
+its relative path must be part of the `{filename}` argument.
 
-- Returns:\
-  The requested file
+HTTP request:
+```http
+DELETE /server/files/{root}/{filename}
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "server.files.delete_file",
+    "params": {
+        "path": "{root}/{filename}"
+    },
+    "id": 1323
+}
+```
+Returns:
 
-### Delete included config file
-Delete a file in the "config" root.  A relative path may be added to the file
-to delete a file in a subdirectory.
-- HTTP command:\
-  `DELETE /server/files/config/<file_name>`
+The name of the deleted file
 
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "server.files.delete_file", params:
-   {path: "config/<file_name>}, id: <request id>}`
+#### Download klippy.log
+HTTP request:
+```http
+GET /server/files/klippy.log
+```
+JSON-RPC request: Not Available
 
-- Returns:\
-  The HTTP request returns the name of the deleted file.
+Returns:
 
-### Download a config example
-- HTTP command:\
-  `GET /server/files/config_examples/<file_name>`
+The requested file
 
-- Websocket command:\
-  Not Available
+#### Download moonraker.log
+HTTP request:
+```http
+GET /server/files/moonraker.log
+```
+JSON-RPC request: Not Available
 
-- Returns:\
-  The requested file
+Returns:
 
-### Download Klipper documentation
-- HTTP command:\
-  `GET /server/files/docs/<file_name>`
+The requested file
 
-- Websocket command:\
-  Not Available
-
-- Returns:\
-  The requested file
-
-### Download klippy.log
-- HTTP command:\
-  `GET /server/files/klippy.log`
-
-- Websocket command:\
-  Not Available
-
-- Returns:\
-  klippy.log
-
-### Download moonraker.log
-- HTTP command:\
-  `GET /server/files/moonraker.log`
-
-- Websocket command:\
-  Not Available
-
-- Returns:\
-  moonraker.log
-
-## Authorization
+### Authorization
 
 Untrusted Clients must use a key to access the API by including it in the
-`X-Api-Key` header for each HTTP Request.  The API below allows authorized
-clients to receive and change the current API Key.
+`X-Api-Key` header for each HTTP Request.  The APIs below allow authorized
+clients to request or modify the current API Key.
 
-### Get the Current API Key
-- HTTP command:\
-  `GET /access/api_key`
+#### Get the Current API Key
+HTTP request:
+```http
+GET /access/api_key
+```
+JSON-RPC request: Not Available
 
-- Websocket command:\
-  Not Available
+Returns:
 
-- Returns:\
-  The current API key
+The current API key
 
-### Generate a New API Key
-- HTTP command:\
-  `POST /access/api_key`
+#### Generate a New API Key
+HTTP request:
+```http
+POST /access/api_key
+```
+JSON-RPC request: Not Available
 
-- Websocket command:\
-  Not available
+Returns:
 
-- Returns:\
-  The newly generated API key.  This overwrites the previous key.  Note that
-  the API key change is applied immediately, all subsequent HTTP requests
-  from untrusted clients must use the new key.
+The newly generated API key.  This overwrites the previous key.  Note that
+the API key change is applied immediately, all subsequent HTTP requests
+from untrusted clients must use the new key.
 
-### Generate a Oneshot Token
+#### Generate a Oneshot Token
 
-Some HTTP Requests do not expose the ability the change the headers, which is
-required to apply the `X-Api-Key`.  To accomodiate these requests it a client
-may ask the server for a Oneshot Token.  Tokens expire in 5 seconds and may
-only be used once, making them relatively for inclusion in the query string.
+Javascript is not capable of modifying the headers for some HTTP requests
+(for example, the `websocket`), which is a requirement to apply `X-Api-Key`
+authorization.  To work around this clients may request a Oneshot Token and
+pass it via the query string for these requests.  Tokens expire in 5 seconds
+and may only be used once, making them relatively safe for inclusion in the
+query string.
 
-- HTTP command:\
-  `GET /access/oneshot_token`
+HTTP request:
+```http
+GET /access/oneshot_token
+```
+JSON-RPC request: Not Available
 
-- Websocket command:
-  Not available
+Returns:
 
-- Returns:\
-  A temporary token that may be added to a requests query string for access
-  to any API endpoint.  The query string should be added in the form of:
-  `?token=randomly_generated_token`
+A temporary token that may be added to a request's query string for access
+to any API endpoint.  The query string should be added in the form of:
+```
+?token={base32_ramdom_token}
+```
 
-## Database APIs
+### Database APIs
 The following endpoints provide access to Moonraker's ldbm database.  The
-database is divided into "namespaces", each client may define its own
+database is divided into `namespaces`.  Each client may define its own
 namespace to store information.  From the client's point of view, a
-namespace is an "object".  Items in the database are accessed by providing
+namespace is an `object`.  Items in the database are accessed by providing
 a namespace and a key.  A key may be specifed as string, where a "." is a
-delimeter to access nested objects. Alternatively the key may be specified
-as an array of strings, where each string references a nested object.
-This is useful for scenarios where your namespace contain keys that include
+delimeter, to access nested fields. Alternatively the key may be specified
+as an array of strings, where each string references a nested field.
+This is useful for scenarios where your namespace contains keys that include
 a "." character.
 
-Moonraker reserves "moonraker" and "gcode_metadata" namespaces.  Clients may
-read from these namespaces but they may not modify them.
+**Note: Moonraker reserves the `moonraker`, `gcode_metadata`, and `history`
+namespaces. Clients may read from these namespaces but they may not modify
+them.**
 
 For example, assume the following object is stored in the "superclient"
 namespace:
 
 ```json
 {
-    settings: {
-        console: {
-            enable_autocomple: True
+    "settings": {
+        "console": {
+            "enable_autocomplete": true
         }
-    }
-    theme: {
-        background_color: "black"
+    },
+    "theme": {
+        "background_color": "black"
     }
 }
 ```
-One may access the "enable_autocomplete" field by supplying `superclient` as
-the "namespace" parameter and `settings.console.enable_autocomplete` or
-`["settings", "console", "enable_autocomplete"]` as the "key" parameter for
+One may access the `enable_autocomplete` field by supplying `superclient` as
+the `namespace` argument and `settings.console.enable_autocomplete` or
+`["settings", "console", "enable_autocomplete"]` as the `key` argument for
 the request.  The entire settings object could be accessed by providing
-`settings` or `["settings"]` as the key parameter.  The entire namespace
-may be read by omitting the "key" parameter, however as explained below it
+`settings` or `["settings"]` as the `key` argument.  The entire namespace
+may be read by omitting the `key` argument, however as explained below it
 is not possible to modify a namespace without specifying a key.
 
-### List Namespaces
+#### List namespaces
 Lists all available namespaces.
 
-- HTTP command:\
-  `GET /server/database/list`
+HTTP request:
+```http
+GET /server/database/list
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "server.database.list",
+    "id": 8694
+}
+```
 
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "server.database.list", id: <request id>}`
+Returns:
 
-- Returns:\
-  An object containing an array of namespaces in the following format:
-  ```json
-  {
-      'namespaces': ["namespace1", "namespace2"...]
-  }
-  ```
+An object containing an array of namespaces in the following format:
+```json
+{
+    "namespaces": [
+        "gcode_metadata",
+        "history",
+        "moonraker",
+        "test_namespace"
+    ]
+}
+```
 
-### Get Database Item
-Retreives an item from a specified namespace. The `key` parameter may be
+#### Get Database Item
+Retreives an item from a specified namespace. The `key` argument may be
 omitted, in which case an object representing the entire namespace will
 be returned in the `value` field.  If the `key` is provided and does not
 exist in the database an error will be returned.
 
-- HTTP command:\
-  `GET /server/database/item?namespace=my_namespace&key=item.location`
+HTTP request:
+```http
+GET /server/database/item?namespace={namespace}&key={key}
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "server.database.get_item",
+    "params": {
+        "namespace": "{namespace}",
+        "key": "{key}"
+    },
+    "id": 5644
+}
+```
+Returns:
 
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "server.database.get_item", params:
-   {namespace: "my_namespace", key: "item.location"}, id: <request id>}`
+An object containing the requested `namespace`, `key`, and `value`.
+```json
+{
+    "namespace": "moonraker",
+    "key": "file_manager.metadata_version",
+    "value": 2
+}
+```
 
-
-
-- Returns:\
-  An object containing the following fields:
-  ```json
-  {
-      'namespace': "requested_namespace",
-      'key': "requested_key"
-      'value': <value at key>
-  }
-  ```
-
-### Add Database Item
-Inserts an item into the database.  If the namespace does not exist
-it will be created.  If the key specifies parent objects, all parents
+#### Add Database Item
+Inserts an item into the database.  If the `namespace` does not exist
+it will be created.  If the `key` specifies a nested field, all parents
 will be created if they do not exist.  If the key exists it will be
-overwritten with the provided `value`.  The key parameter must be provided,
-it is not possible to assign a value directly to a namespace.
+overwritten with the provided `value`.  The `key` parameter must be provided,
+as it is not possible to assign a value directly to a namespace.
 
-- HTTP command:\
-  `POST /server/database/item?namespace=my_namespace&key=item.location&value:int=100`
+HTTP request:
+```http
+POST /server/database/item?namespace={namespace}&key={key}value={value}`
+```
+**Note: If the `value` is not a string type, the `value` argument must
+provide a [type hint](#query-string-type-hints).  Alternatively,
+arguments may be passed via the request body in JSON format. For
+example:**
+```http
+POST /server/database/item
+Content-Type: application/json
 
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "server.database.post_item", params:
-   {namespace: "my_namespace", key: "item.location", value: 100},
-   id: <request id>}`
+{
+    "namespace": "my_client",
+    "key": "settings.some_count",
+    "value": 100
+}
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "server.database.post_item",
+    "params": {
+        "namespace": "{namespace}",
+        "key": "{key}",
+        "value": 100
+    },
+    "id": 4654
+}
+```
+Returns:
 
-- Returns:\
-  An object containing the following fields:
-  ```json
-  {
-      'namespace': "requested_namespace",
-      'key': "requested_key"
-      'value': <the value inserted>
-  }
-  ```
+An object containing the inserted `namespace`, `key`, and `value`.
+```json
+{
+    "namespace": "test",
+    "key": "settings.some_count",
+    "value": 9001
+}
+```
 
-### Delete Database Item
-Deletes an item from the database at the specified key. If the key does not
-exist in the database an error will be returned.  If the deleted item results
+#### Delete Database Item
+Deletes an item from a `namespace` at the specified `key`. If the key does not
+exist in the namespace an error will be returned.  If the deleted item results
 in an empty namespace, the namespace will be removed from the database.
 
-- HTTP command:\
-  `DELETE /server/database/item?namespace=my_namespace&key=item.location`
+HTTP request:
+```http
+DELETE /server/database/item?namespace={namespace}&key={key}
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "server.database.delete_item",
+    "params": {
+        "namespace": "{namespace}",
+        "key": "{key}"
+    },
+    "id": 4654
+}
+```
+Returns:
+An object containing the `namespace`, `key`, and `value` of the
+deleted item.
+```json
+{
+    "namespace": "test",
+    "key": "settings.some_count",
+    "value": 9001
+}
+```
 
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "server.database.delete_item", params:
-   {namespace: "my_namespace", key: "item.location"}, id: <request id>}`
-
-
-- Returns:\
-  An object containing the following fields:
-  ```json
-  {
-      'namespace': "requested_namespace",
-      'key': "requested_key"
-      'value': <value at deleted key>
-  }
-  ```
-
-## Update Manager APIs
+### Update Manager APIs
 The following endpoints are available when the `[update_manager]` plugin has
 been configured:
 
-### Get update status
+#### Get update status
 Retreives the current state of each "package" available for update.  Typically
-this will consist of information regarding `moonraker`, `klipper`, a `client`,
-and `system` packages.  If moonraker has not yet received information from
-Klipper then its status will be omitted.  If a client has not been configured
-then its status will also be omitted.  One may request that the update info
-be refreshed by sending a `refresh=true` argument.  Note that the refresh
-parameter is ignored if an update is in progress or if a print is in progress.
-In these cases the current status will be returned immediately.
+this will consist of information regarding `moonraker`, `klipper`, `system`
+packages, along with configured clients.  If moonraker has not yet received
+information from Klipper then its status will be omitted.  One may request that
+the update info be refreshed by setting the `refresh` argument to `true`.  Note
+that the `refresh` argument is ignored if an update is in progress or if a print
+is in progress. In these cases the current status will be returned immediately
+and no refresh will take place.  If the `refresh` argument is omitted its value
+defaults to `false`.
 
-- HTTP command:\
-  `GET /machine/update/status?refresh=false`
+HTTP request:
+```http
+GET /machine/update/status?refresh=false
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "machine.update.status",
+    "params": {
+        "refresh": false
+    },
+    "id": 4644
+}
+```
+Returns:
 
-  If the query string is omitted then "refresh" will default
-  to false.
+Status information for each update package.  Note that `mainsail`
+and `fluidd` are present as clients configured in `moonraker.conf`
+```json
+{
+    "github_rate_limit": 60,
+    "github_requests_remaining": 57,
+    "github_limit_reset_time": 1615836932,
+    "version_info": {
+        "system": {
+            "package_count": 4,
+            "package_list": [
+                "libtiff5",
+                "raspberrypi-sys-mods",
+                "rpi-eeprom-images",
+                "rpi-eeprom"
+            ]
+        },
+        "moonraker": {
+            "remote_alias": "origin",
+            "branch": "master",
+            "owner": "Arksine",
+            "version": "v0.4.1-45",
+            "remote_version": "v0.4.1-45",
+            "current_hash": "7e230c1c77fa406741ab99fb9156951c4e5c9cb4",
+            "remote_hash": "7e230c1c77fa406741ab99fb9156951c4e5c9cb4",
+            "is_dirty": false,
+            "detached": false,
+            "commits_behind": [],
+            "is_valid": true,
+            "debug_enabled": true
+        },
+        "mainsail": {
+            "name": "mainsail",
+            "owner": "meteyou",
+            "version": "v1.3.0",
+            "remote_version": "v1.4.0"
+        },
+        "fluidd": {
+            "name": "fluidd",
+            "owner": "cadriel",
+            "version": "v1.6.1",
+            "remote_version": "v1.10.0"
+        },
+        "klipper": {
+            "remote_alias": "origin",
+            "branch": "master",
+            "owner": "KevinOConnor",
+            "version": "v0.9.1-317",
+            "remote_version": "v0.9.1-324",
+            "current_hash": "d77928b17ba6b32189033b3d6decdb5bcc7c342c",
+            "remote_hash": "22753f3b389e3f21a6047bac70abc42b6cf4a7dc",
+            "is_dirty": false,
+            "detached": false,
+            "commits_behind": [
+                {
+                    "sha": "22753f3b389e3f21a6047bac70abc42b6cf4a7dc",
+                    "author": "Kevin O'Connor",
+                    "date": "1615830538",
+                    "subject": "tmc: Only check for tmc2130 reset via CS_ACTUAL if IHOLD > 0",
+                    "message": "Signed-off-by: Kevin O'Connor <kevin@koconnor.net>",
+                    "tag": null
+                },
+                {
+                    "sha": "b4437f8eeeaddf60f893ceaeaf4d9ed06d57eeae",
+                    "author": "Michael Kurz",
+                    "date": "1615823429",
+                    "subject": "bme280: Add support for BMP280 and BME680 sensors (#4040)",
+                    "message": "This adds support for BMP280 and BME680 sensor ICs,\r\nalong with fixing calibration data readout for BME280.\r\n\r\nGas sensor readout for the BME680 is just the raw compensated value.\r\nTo get actual meaningful values, more research is needed.\r\n\r\nSigned-off-by: Michael Kurz <michi.kurz@gmail.com>",
+                    "tag": null
+                }
+            ],
+            "is_valid": true,
+            "debug_enabled": true
+        }
+    },
+    "busy": false
+}
+```
+Below is an explanation for each field:
 
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "machine.update.status",
-   params: {refresh: false} , id: <request id>}`
+- `busy`: set to true if an update is in progress.  Moonraker will not
+  allow concurrent updates.
+- `github_rate_limit`: the maximum number of github API requests
+  the user currently is allowed.  An unathenticated user typically has 60
+  requests per hour.
+- `github_requests_remaining`: the number of API request the user
+  currently has remaining.
+- `github_limit_reset_time`:  the time when the rate limit will reset,
+  reported as seconds since the epoch (aka Unix Time).
 
-  If the "params" are omitted then "refresh" will default to false.
+The `moonraker`, `klipper` packages, along with and clients configured
+as git repos have the following fields:
 
-- Returns:\
-  Status information in the following format:
-  ```json
-  {
-      'version_info': {
-          'moonraker': {
-              owner: <string>,
-              branch: <string>,
-              remote_alias: <string>,
-              version: <string>,
-              remote_version: <string>,
-              current_hash: <string>,
-              remote_hash: <string>,
-              is_valid: <bool>,
-              is_dirty: <bool>,
-              detached: <bool>,
-              debug_enabled: <bool>,
-              commits_behind: [
-                  {
-                      author: <string>,
-                      date: <unix_time>,
-                      subject: <string>,
-                      message: <string>,
-                      sha: <string>,
-                      tag: <string or null>
-                  },...
-              ]
-          },
-          'klipper': {
-              owner: <string>,
-              branch: <string>,
-              remote_alias: <string>,
-              version: <string>,
-              remote_version: <string>,
-              current_hash: <string>,
-              remote_hash: <string>,
-              is_valid: <bool>,
-              is_dirty: <bool>,
-              detached: <bool>,
-              debug_enabled: <bool>,
-              commits_behind: []
-          },
-          'client_name_1': {
-              name: <string>,
-              version: <string>,
-              remote_version: <string>
-          },
-          'system': {
-              package_count: <int>,
-              package_list: <array>
-          }
-      },
-      busy: false,
-      github_rate_limit: <int>,
-      github_requests_remaining: <int>
-      github_limit_reset_time: <int>,
-  }
-  ```
-  - The `busy` field is set to true if an update is in progress.  Moonraker
-    will not allow concurrent updates.
-  - The `github_rate_limit` is the maximum number of github API requests
-    the user currently has.  An unathenticated user typically has 60
-    requests per hour.
-  - The `github_requests_remaining` is the number of API request the user
-    currently has remaining.
-  - The `github_limit_reset_time` is reported as seconds since the epoch.
-    When this time is reached the user's limit will be reset.
-  - The `moonraker` and `klipper` objects have the following fields:
-    - `branch`: the name of the current git branch.  This should typically
-      be "master".
-    - `remote_alias`: the alias for the remote.  This should typically be
-      "origin".
-    - `version`:  version of the current repo on disk
-    - `remote_version`: version of the latest available update
-    - `current_hash`: hash of the most recent commit on disk
-    - `remote_hash`: hash of the most recent commit pushed to the remote
-    - `is_valid`: True if installation is a valid git repo on the master branch
-      and an "origin" set to the official remote
-    - `is_dirty`: True if the repo has been modified
-    - `detached`: True if the repo is currently in a detached state
-    - `debug_enabled`: True when "enable_repo_debug" has been configured.  This
-      will bypass repo validation, allowing detached updates, and updates from
-      a remote/origin other than "origin/master".
-  - Multiple `client` fields may be present.  Web clients have the following
-    fields:
-    - `name`: Name of the configured client
-    - `version`:  version of the installed client.
-    - `remote_version`:  version of the latest release published to GitHub
-    A `git_repo` client will have fields that match that of `klipper` and
-    `moonraker`
-  - The `system` object has the following fields:
-    - `package_count`: The number of system packages available for update
-    - `package_list`: An array containing the names of packages available
-      for update
+- `owner`: the owner of the repo
+- `branch`: the name of the current git branch.  This should typically
+    be "master".
+- `remote_alias`: the alias for the remote.  This should typically be
+    "origin".
+- `version`:  version of the current repo on disk
+- `remote_version`: version of the latest available update
+- `current_hash`: hash of the most recent commit on disk
+- `remote_hash`: hash of the most recent commit pushed to the remote
+- `is_valid`: true if installation is a valid git repo on the master branch
+    and an "origin" set to the official remote
+- `is_dirty`: true if the repo has been modified
+- `detached`: true if the repo is currently in a detached state
+- `debug_enabled`: True when `enable_repo_debug` has been configured.  This
+    will bypass repo validation allowing detached updates, and updates from
+    a remote/branch other than than the primary (typically origin/master).
+- `commits_behind`: A list of commits behind.  Up to 30 "untagged" commits
+  will be reported.  Moonraker checks the last 100 commits for tags, any
+  commits beyond the last 30 with a tag will also be reported.
 
-### Update Moonraker
+Web clients have the following fields:
+
+- `name`: name of the configured client
+- `owner`: the owner of the client
+- `version`:  version of the installed client.
+- `remote_version`:  version of the latest release published to GitHub
+
+The `system` package has the following fields:
+
+- `package_count`: the number of system packages available for update
+- `package_list`: an array containing the names of packages available
+  for update
+
+#### Update Moonraker
 Pulls the most recent version of Moonraker from GitHub and restarts
-the service.  If "include_deps" is set to `true` an attempt will be made
-to install new packages (via apt-get) and python dependencies (via pip).
-Note that Moonraker uses semantic versioning to check for dependency changes
-automatically, so it is generally not necessary to set `include_deps`
-to `true`.  If an update is requested while a print is in progress then
+the service. If an update is requested while a print is in progress then
 this request will return an error.
 
-- HTTP command:\
-  `POST /machine/update/moonraker?include_deps=false`
+HTTP request:
+```http
+POST /machine/update/moonraker
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "machine.update.moonraker",
+    "id": 4645
+}
+```
+Returns:
 
-  If the query string is omitted then "include_deps" will default
-  to false.
+`ok` when complete
 
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "machine.update.moonraker",
-   params: {include_deps: false}, id: <request id>}`
-
-  If the "params" are omitted then "include_deps" will default to false.
-
-- Returns:\
-  `ok` when complete
-
-### Update Klipper
+#### Update Klipper
 Pulls the most recent version of Klipper from GitHub and restarts
-the service.  If "include_deps" is set to `true` an attempt will be made
-to install new packages (via apt-get) and python dependencies (via pip).
-At the moment there is no method for automatically checking for updated
-Klipper dependencies, so clients might wish to make this option available
-to users via the UI. If an update is requested while a print is in progress
+the service. If an update is requested while a print is in progress
 then this request will return an error.
 
-- HTTP command:\
-  `POST /machine/update/klipper?include_deps=false`
+HTTP request:
+```http
+POST /machine/update/klipper
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "machine.update.klipper",
+    "id": 5745
+}
+```
+Returns:
 
-  If the query string is omitted then "include_deps" will default
-  to false.
+`ok` when complete
 
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "machine.update.klipper",
-   params: {include_deps: false}, id: <request id>}`
-
-  If the "params" are omitted then "include_deps" will default to false.
-
-- Returns:\
-  `ok` when complete
-
-### Update Client
+#### Update Client
 If one more more `[update_manager client client_name]` sections have
 been configured this endpoint can be used to install the most recently
 published release of the client.  If an update is requested while a
@@ -1328,467 +1840,624 @@ print is in progress then this request will return an error.  The
 `name` argument is requred, it's value should match the `client_name`
 of the configured section.
 
-- HTTP command:\
-  `POST /machine/update/client?name=client_name`
+HTTP request:
+```http
+POST /machine/update/client?name={client_name}
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method":  "machine.update.client",
+    "params": {
+        "name": "client_name"
+    },
+    "id": 8546
+}
+```
+Returns:
 
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "machine.update.client",
-   params: {name: "client_name"}, id: <request id>}`
+`ok` when complete
 
-- Returns:\
-  `ok` when complete
-
-### Update System Packages
-Upgrades the system packages.  Currently only `apt-get` is supported.
+#### Update System Packages
+Upgrades system packages.  Currently only `apt-get` is supported.
 If an update is requested while a print is in progress then this request
 will return an error.
 
-- HTTP command:\
-  `POST /machine/update/system`
+HTTP request:
+```http
+POST /machine/update/system
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "machine.update.system",
+    "id": 4564
+}
+```
+Returns:
 
-- Websocket command:\
-  `{jsonrpc: "2.0", method: "machine.update.system",
-   id: <request id>}`
+`ok` when complete
 
-- Returns:\
-  `ok` when complete
-
-## Power APIs
+### Power APIs
 The APIs below are available when the `[power]` plugin has been configured.
 
-### Get Devices
-- HTTP command:\
-  `GET /machine/device_power/devices`
+#### Get Device List
+HTTP request:
+```http
+GET /machine/device_power/devices
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method":"machine.device_power.devices",
+    "id": 5646
+}
+```
+Returns:
 
-- Websocket command:\
-  `{"jsonrpc":"2.0","method":"machine.device_power.devices","id":"1"}`
-
-- Returns:\
-  An array of objects containing info for each configured device.
-  ```json
-  {
-    devices: [
-      {
-        device: <device_name>,
-        status: <device_status>,
-        type: <device_type>
-      }, ...
+An array of objects containing info for each configured device.
+```json
+{
+    "devices": [
+        {
+            "device": "green_led",
+            "status": "off",
+            "locked_while_printing": true,
+            "type": "gpio"
+        },
+        {
+            "device": "printer",
+            "status": "off",
+            "locked_while_printing": false,
+            "type": "tplink_smartplug"
+        }
     ]
-  }
-  ```
+}
+```
 
-### Get Device Status
-- HTTP command:\
-  `GET /machine/device_power/status?dev_one&dev_two`
+#### Get Device Status
+Get power status for the requested devices.  At least one device must be
+specified.
 
-- Websocket command:\
-  `{"jsonrpc":"2.0","method":"machine.device_power.status","id":"1",
-    "params":{"dev_one":null, "dev_two": null}}`
+HTTP request:
+```http
+GET /machine/device_power/status?dev_one&dev_two
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "machine.device_power.status",
+    "params": {
+        "dev_one":null,
+        "dev_two": null
+    },
+    "id": 4564
+}
+```
+Returns:
 
-- Returns:\
-  An object containing status for each requested device
-  ```json
-  {
-    dev_one: <device_status>,
-    dev_two: <device_status>,
-    ...
-  }
-  ```
+An object containing power state for each requested device:
+```json
+{
+    "green_led": "off",
+    "printer": "off"
+}
+```
 
-### Power On Device(s)
-- HTTP command:\
-  `POST /machine/device_power/on?dev_one&dev_two`
+#### Power On Devices
+Power on the requested devices.  At least one device must be
+specified.
 
-- Websocket command:\
-  `{"jsonrpc":"2.0","method":"machine.device_power.on","id":"1",
-    "params":{"dev_one":null, "dev_two": null}}`
+HTTP request:
+```http
+POST /machine/device_power/on?dev_one&dev_two
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "machine.device_power.on",
+    "params": {
+        "dev_one":null,
+        "dev_two": null
+    },
+    "id": 4564
+}
+```
+An object containing power state for each requested device:
+```json
+{
+    "green_led": "on",
+    "printer": "on"
+}
+```
 
-- Returns:\
-  An object containing status for each requested device
-  ```json
-  {
-    dev_one: <device_status>,
-    dev_two: <device_status>,
-    ...
-  }
-  ```
+#### Power Off Devices
+Power off the requested devices.  At least one device must be
+specified.
 
-### Power Off Device(s)
-- HTTP command:\
-  `POST /machine/device_power/off?dev_one&dev_two`
+HTTP request:
+```http
+POST /machine/device_power/off?dev_one&dev_two
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "machine.device_power.off",
+    "params": {
+        "dev_one":null,
+        "dev_two": null
+    },
+    "id": 4564
+}
+```
+An object containing power state for each requested device:
+```json
+{
+    "green_led": "off",
+    "printer": "off"
+}
+```
 
-- Websocket command:\
-  `{"jsonrpc":"2.0","method":"machine.device_power.off","id":"1",
-    "params":{"dev_one":null, "dev_two": null}}`
-
-- Returns:\
-  An object containing status for each requested device
-  ```json
-  {
-    dev_one: <device_status>,
-    dev_two: <device_status>,
-    ...
-  }
-  ```
-
-## Octoprint API emulation
+### Octoprint API emulation
 Partial support of Octoprint API is implemented with the purpose of
 allowing uploading of sliced prints to a moonraker instance.
 Currently we support Slic3r derivatives and Cura with Cura-Octoprint.
 
-### Version information
-- HTTP command:\
-  `GET /api/version`
+#### Version information
+HTTP request:
+```http
+GET /api/version
+```
+JSON-RPC request: Not Available
 
-- Returns:\
-  An object containing simulated Octoprint version information
-  ```json
-  {
-    server: "1.5.0",
-    api: "0.1",
-    text: "Octoprint (Moonraker v0.3.1-12)"
-  }
-  ```
+Returns:
 
-### Server status
-- HTTP command:\
-  `GET /api/server`
+An object containing simulated Octoprint version information
+```json
+{
+    "server": "1.5.0",
+    "api": "0.1",
+    "text": "Octoprint (Moonraker v0.3.1-12)"
+}
+```
 
-- Returns:\
-  An object containing simulated Octoprint server status
-  ```json
-  {
-    server: "1.5.0",
-    safemode: <None or "settings">
-  }
-  ```
+#### Server status
+HTTP request:
+```http
+GET /api/server
+```
+JSON-RPC request: Not Available
 
-### Login verification & User information
-- HTTP command:\
-  `GET /api/login`
+Returns:
 
-- Returns:\
-  An object containing stubbed Octoprint login/user verification
-  ```json
-  {
-    _is_external_client: false,
-    _login_mechanism: "apikey",
-    name: "_api",
-    active: true,
-    user: true,
-    admin: true,
-    apikey: null,
-    permissions: [],
-    groups: ["admins", "users"],
-  }
-  ```
+An object containing simulated Octoprint server status
+```json
+{
+    "server": "1.5.0",
+    "safemode": "settings"
+}
+```
 
-### Get settings
-- HTTP command:\
-  `GET /api/settings`
+#### Login verification & User information
+HTTP request:
+```http
+GET /api/login
+```
+JSON-RPC request: Not Available
 
-- Returns:\
-  An object containing stubbed Octoprint settings.
-  The webcam route is hardcoded to Fluidd/Mainsail default path.
-  We say we have the UFP plugin installed so that Cura-Octoprint will
-  upload in the preferred UFP format.
-  ```json
-  {
-    plugins: {
-      UltimakerFormatPackage: {
-        align_inline_thumbnail: false,
-        inline_thumbnail: false,
-        inline_thumbnail_align_value: "left",
-        inline_thumbnail_scale_value: "50",
-        installed: true,
-        installed_version: "0.2.2",
-        scale_inline_thumbnail: false,
-        state_panel_thumbnail: true
-      }
+Returns:
+
+An object containing stubbed Octoprint login/user verification
+```json
+{
+    "_is_external_client": false,
+    "_login_mechanism": "apikey",
+    "name": "_api",
+    "active": true,
+    "user": true,
+    "admin": true,
+    "apikey": null,
+    "permissions": [],
+    "groups": ["admins", "users"],
+}
+```
+
+#### Get settings
+HTTP request:
+```http
+GET /api/settings
+```
+JSON-RPC request: Not Available
+
+Returns:
+
+An object containing stubbed Octoprint settings.
+The webcam route is hardcoded to Fluidd/Mainsail default path.
+We say we have the UFP plugin installed so that Cura-Octoprint will
+upload in the preferred UFP format.
+```json
+{
+    "plugins": {
+        "UltimakerFormatPackage": {
+            "align_inline_thumbnail": false,
+            "inline_thumbnail": false,
+            "inline_thumbnail_align_value": "left",
+            "inline_thumbnail_scale_value": "50",
+            "installed": true,
+            "installed_version": "0.2.2",
+            "scale_inline_thumbnail": false,
+            "state_panel_thumbnail": true
+        }
     },
-    feature: {
-      sdSupport: false,
-      temperatureGraph: false
+    "feature": {
+        "sdSupport": false,
+        "temperatureGraph": false
     },
-    webcam: {
-      flipH: false,
-      flipV: false,
-      rotate90: false,
-      streamUrl: "/webcam/?action=stream",
-      webcamEnabled': true
+    "webcam": {
+        "flipH": false,
+        "flipV": false,
+        "rotate90": false,
+        "streamUrl": "/webcam/?action=stream",
+        "webcamEnabled": true
     }
-  }
-  ```
+}
+```
 
-### File Upload
-- HTTP command:\
-  `POST /api/files/local`
-  Otherwise identical to the standard Moonraker `POST /server/files/upload` API.
+#### Octoprint File Upload
+HTTP request:
+```http
+POST /api/files/local
+```
+JSON-RPC request: Not Available
 
-### Get Job status
-- HTTP command:\
-  `GET /api/job`
+Alias for Moonrakers [file upload API](#file-upload).
 
-- Returns:\
-  An object containing stubbed Octoprint Job status
-  ```json
-  {
-    job: {
-      file: {name: null},
-      estimatedPrintTime: null,
-      filament: {length: null},
-      user: None
+#### Get Job status
+HTTP request:
+```http
+GET /api/job
+```
+JSON-RPC request: Not Available
+
+Returns:
+
+An object containing stubbed Octoprint Job status
+```json
+{
+    "job": {
+        "file": {"name": null},
+        "estimatedPrintTime": null,
+        "filament": {"length": null},
+        "user": null
     },
-    progress: {
-      completion: null,
-      filepos: null,
-      printTime: null,
-      printTimeLeft: null,
-      printTimeOrigin: null
+    "progress": {
+        "completion": null,
+        "filepos": null,
+        "printTime": null,
+        "printTimeLeft": null,
+        "printTimeOrigin": null
     },
-    state: <One of "Offline","Error", "Operational", "Printing", "Paused">
-  }
-  ```
+    "state": "Offline"
+}
+```
 
-### Get Printer status
-- HTTP command:\
-  `GET /api/printer`
+#### Get Printer status
+HTTP request:
+```http
+GET /api/printer
+```
+JSON-RPC request: Not Available
 
-- Returns:\
-  An object containing Octoprint Printer status
-  ```json
-  {
-    temperature: {
-      <list of heater names: "bed", "tool<n>">: {
-        actual: <actual temp>,
-        offset: 0,
-        target: <target temp>
-      }
+Returns:
+
+An object containing Octoprint Printer status
+```json
+{
+    "temperature": {
+        "tool0": {
+            "actual": 22.25,
+            "offset": 0,
+            "target": 0
+        },
+        "bed": {
+            "actual": 22.25,
+            "offset": 0,
+            "target": 0
+        }, ...<additional heaters>
     },
-    state: {
-      text': state,
-      flags': {
-        operational: <bool>,
-        paused: <bool>,
-        printing: <bool>,
-        cancelling: <bool>,
-        pausing: False,
-        error: <bool>,
-        ready: <bool>,
-        closedOrError: <bool>
-      }
+    "state": {
+        "text": "state",
+        "flags": {
+            "operational": true,
+            "paused": false,
+            "printing": false,
+            "cancelling": false,
+            "pausing": false,
+            "error": false,
+            "ready": false,
+            "closedOrError": false
+        }
     }
-  }
-  ```
+}
+```
 
-### Send GCode command
-- HTTP command:\
-  `POST /api/printer/command`
+#### Send GCode command
+HTTP request:
+```http
+POST /api/printer/command
+Content-Type: applicaton/json
 
-  JSON payload with parameter:
-  * commands: List of GCode strings
+{
+    "commands": ["G28"]
+}
+```
+JSON-RPC request: Not Available
 
-- Returns:\
-  An blank JSON object
-  ```json
-  {}
-  ```
+Returns:
 
-### List Printer profiles
-- HTTP command:\
-  `GET /api/printerprofiles`
+An empty JSON object
+```json
+{}
+```
 
-- Returns:\
-  An object containing simulates Octoprint Printer profile
-  ```json
-  {
-    profiles: {
-      _default: {
-        id: "_default",
-        name: "Default",
-        color: "default",
-        model: "Default",
-        default': true,
-        current': true,
-        heatedBed: <true if "heater_bed" heater exists>,
-        heatedChamber: <true if "chamber" heater exists>
-      }
+#### List Printer profiles
+HTTP request:
+```http
+GET /api/printerprofiles
+```
+JSON-RPC request: Not Available
+
+Returns:
+
+An object containing simulates Octoprint Printer profile
+```json
+{
+    "profiles": {
+        "_default": {
+            "id": "_default",
+            "name": "Default",
+            "color": "default",
+            "model": "Default",
+            "default": true,
+            "current": true,
+            "heatedBed": true,
+            "heatedChamber": false
+        }
     }
-  }
-  ```
+}
+```
 
-## History APIs
+### History APIs
 The APIs below are avilable when the `[history]` plugin has been configured.
 
-### Get job list
-- HTTP command:\
-  `GET /server/history/list?limit=50&start=50&since=1&before=5`
+#### Get job list
+HTTP request:
+```http
+GET /server/history/list?limit=50&start=50&since=1&before=5
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method":"server.history.list",
+    "params":{
+        "limit": 50,
+        "start": 10,
+        "since": 464.54,
+        "before": 1322.54
+    },
+    "id": 5656
+}
+```
 
-- Websocket command:\
-  `{"jsonrpc":"2.0","method":"server.history.list","id":"1","params":{}}`
+All arguments are optional. Arguments are as follows:
 
-  All arguments are optional. Arguments are as follows:
-  - `before` All jobs before this UNIX timestamp
-  - `limit` Number of prints to return
-  - `since` All jobs after this UNIX timestamp
-  - `start` Record number to start from (i.e. 10 would start at the 10th print)
+- `start` Record number to start from (i.e. 10 would start at the 10th print)
+- `limit` Maximum Number of prints to return
+- `before` All jobs before this UNIX timestamp
+- `since` All jobs after this UNIX timestamp
 
-- Returns an array of jobs that have been printed
-  ```json
-  {
-      count: <number of prints>,
-      jobs: [
+Returns:
+
+An array of requsted historical jobs:
+```json
+{
+    "count": 1,
+    "jobs": [
         {
-            "job_id": <unique job id>,
-            "exists": <boolean>,
-            "end_time": <end_time>,
-            "filament_used": <filament_used>,
-            "filename": <filename>,
-            "metadata": {}, // Object containing metadata at time of job
-            "print_duration": <print_duration>,
-            "status": <status>,
-            "start_time": <start_time>,
-            "total_duration": <total_duration>
+            "job_id": "000001",
+            "exists": true,
+            "end_time": 1615764265.6493807,
+            "filament_used": 7.83,
+            "filename": "test/history_test.gcode",
+            "metadata": {
+                // Object containing metadata at time of job
+            },
+            "print_duration": 18.37201827496756,
+            "status": "completed",
+            "start_time": 1615764496.622146,
+            "total_duration": 18.37201827496756
         },
-        ...
-      ]
-  }
-  ```
+    ]
+}
+```
 
-### Get a single job
-- HTTP command:\
-  `GET /server/history/job?uid=<id>`
+#### Get a single job
+HTTP request:
+```http
+GET /server/history/job?uid=<id>
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method":"server.history.get_job",
+    "params":{"uid": "{uid}"},
+    "id": 4564,
+}
+```
+Returns:
 
-- Websocket command:\
-  `{"jsonrpc":"2.0","method":"server.history.get_job","id":"1",
-  "params":{"uid": <id>}}`
+Data associated with the job ID in the following format:
+```json
+{
+    "job": {
+        "job_id": "000001",
+        "exists": true,
+        "end_time": 1615764265.6493807,
+        "filament_used": 7.83,
+        "filename": "test/history_test.gcode",
+        "metadata": {
+            // Object containing metadata at time of job
+        },
+        "print_duration": 18.37201827496756,
+        "status": "completed",
+        "start_time": 1615764496.622146,
+        "total_duration": 18.37201827496756
+    }
+}
+```
 
-- Returns:
-  Data associated with the job ID in the following format:
-  ```json
-  {
-      "job": {
-        "job_id": <unique job id>,
-        "exists": <boolean>,
-        "end_time": <end_time>,
-        "filament_used": <filament_used>,
-        "filename": <filename>,
-        "metadata": {}, // Object containing metadata at time of job
-        "print_duration": <print_duration>,
-        "status": <status>,
-        "start_time": <start_time>,
-        "total_duration": <total_duration>
-      }
-  }
-  ```
+#### Delete job
+HTTP request:
+```http
+DELETE /server/history/job?uid=<id>
+```
+JSON-RPC request:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "server.history.delete_job",
+    "params":{
+        "uid": "{uid}"
+    },
+    "id": 5534
+}
+```
 
-### Delete job
-- HTTP command:\
-  `DELETE /server/history/job?uid=<id>`
+**Note: it is possible to replace the `uid` argument with `all=true`
+to delete all jobs in the history database.**
 
-  Note: it is possible to replace the "uid" argument with `all=true`
-  to delete all jobs.
+Returns:
 
-- Websocket command:\
-  `{"jsonrpc":"2.0","method":"server.history.delete_job","id":"1",
-  "params":{"uid": <id>}}`
-
-  Note: it is possible to replace the "uid" argument with `"all": true`
-  to delete all jobs.
-
-- Returns an array of deleted ids
+An array of deleted job ids
 ```json
 [
-    id1,
-    id2,
-    ...
+    "000000",
+    "000001",
 ]
 ```
 
-## Websocket notifications
+### Websocket notifications
 Printer generated events are sent over the websocket as JSON-RPC 2.0
 notifications.  These notifications are sent to all connected clients
 in the following format:
-
-`{jsonrpc: "2.0", method: <event method name>}`
-
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "{event method name}"
+}
+```
 OR
-
-`{jsonrpc: "2.0", method: <event method name>, params: [<event parameter>]}`
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "{event method name}",
+    "params": [<event parameter>]
+}
+```
 
 If a notification has parameters,  the `params` value will always be
 wrapped in an array as directed by the JSON-RPC standard.  Currently
 all notifications available are broadcast with either no parameters
 or a single parameter.
 
-### Gcode response:
-All calls to gcode.respond() are forwarded over the websocket.  They arrive
+#### Gcode Response
+All of Klippy's gcode responses are forwarded over the websocket.  They arrive
 as a "gcode_response" notification:
-
-`{jsonrpc: "2.0", method: "notify_gcode_response", params: ["response"]}`
-
-### Status subscriptions:
-Status Subscriptions arrive as a "notify_status_update" notification:
-
-`{jsonrpc: "2.0", method: "notify_status_update", params: [<status_data>]}`
-
-The structure of the status data is identical to the structure that is
-returned from an object query's "status" attribute.
-
-### Klippy Ready:
-Notify clients when Klippy has reported a ready state
-
-`{jsonrpc: "2.0", method: "notify_klippy_ready"}`
-
-### Klippy Shutdown:
-Notify clients when Klippy has reported a shutdown state
-
-`{jsonrpc: "2.0", method: "notify_klippy_shutdown"}`
-
-### Klippy Disconnected:
-Notify clients when Moonraker's connection to Klippy has terminated
-
-`{jsonrpc: "2.0", method: "notify_klippy_disconnected"}`
-
-### File List Changed
-When a client makes a change to the virtual sdcard file list
-(via upload or delete) a notification is broadcast to alert all connected
-clients of the change:
-
-`{jsonrpc: "2.0", method: "notify_filelist_changed",
- params: [<file changed info>]}`
-
-The <file changed info> param is an object in the following format, where
-the "action" is the operation that prompted the change, and the "item"
-contains information about the item that has changed:
-
 ```json
-{action: "<action>",
-  item: {
-    path: "<file or directory path>",
-    root: "<root_name>",
-    size: <file size>,
-    modified: "<date modified>"
- }
-```
-Note that file move and copy actions also include a "source item" that
-contains the path and root of the source file or directory.
-```json
-{action: "<action>",
-  item: {
-    path: "<file or directory path>",
-    root: "<root_name>",
-    size: <file size>,
-    modified: "<date modified>"
- },
-  source_item: {
-    path: "<file or directory path>",
-    root: "<root_name>"
-  }
+{
+    "jsonrpc": "2.0",
+    "method": "notify_gcode_response",
+    "params": ["response message"]
 }
 ```
 
-The following `actions` are currently available:
+#### Subscriptions
+Status Subscriptions arrive as a "notify_status_update" notification:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "notify_status_update",
+    "params": [{<status object>}]
+}
+```
+The structure of the `status object` is identical to the structure that is
+returned from an [object query's](#query-printer-object-status)
+`status` field.
+
+#### Klippy Ready
+Notify clients when Klippy has reported a ready state
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "notify_klippy_ready"
+}
+```
+
+#### Klippy Shutdown
+Notify clients when Klippy has reported a shutdown state
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "notify_klippy_shutdown"
+}
+```
+
+#### Klippy Disconnected
+Notify clients when Moonraker's connection to Klippy has terminated
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "notify_klippy_disconnected"
+}
+```
+
+#### File List Changed
+When a client makes a change to a file or directory in a registered
+`root` (via upload, delete, move, etc) a notification is broadcast
+to alert all connected clients of the change:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "notify_filelist_changed",
+    "params": [
+        {
+            "action": "{action}",
+            "item": {
+                "path": "{file or directory path}",
+                "root": "{root}",
+                "size": 46458,
+                "modified": 545465
+            },
+            "source_item": {
+                "path": "{file or directory path}",
+                "root": "{root_name}"
+            }
+        }
+    ]
+}
+```
+The `source_item` field is only present for `move_item` and
+`copy_item` actions.  The following `action` field will be set
+to one of the following values:
+
 - `upload_file`
 - `delete_file`
 - `create_dir`
@@ -1796,51 +2465,39 @@ The following `actions` are currently available:
 - `move_item`
 - `copy_item`
 
-### Metadata Update
+#### Metadata Update
 When a new file is uploaded via the API a websocket notification is broadcast
 to all connected clients after parsing is complete:
-
-`{jsonrpc: "2.0", method: "notify_metadata_update", params: [metadata]}`
-
-Where `metadata` is an object in the following format:
-
 ```json
 {
-  filename: "file name",
-  size: <file size>,
-  modified: "last modified date",
-  slicer: "Slicer Name",
-  first_layer_height: <in mm>,
-  layer_height: <in mm>,
-  object_height: <in mm>,
-  estimated_time: <time in seconds>,
-  filament_total: <in mm>,
-  thumbnails: [
-    {
-      width: <in pixels>,
-      height: <in pixels>,
-      size: <length of string>,
-      data: <base64 string>
-    }, ...
-  ]
+    "jsonrpc": "2.0",
+    "method": "notify_metadata_update",
+    "params": [{metadata}]
 }
 ```
 
-### Update Manager Responses
+Where `metadata` is an object matching that returned from a
+[gcode metadata request](#get-gcode-metadata).
+
+#### Update Manager Response
 The update manager will send asyncronous messages to the client during an
 update:
-
-`{jsonrpc: "2.0", method: "notify_update_response", params: [response]}`
-
-Where `response` is an object int he following format:
 ```json
 {
-    application: <string>,
-    proc_id: <int>,
-    message: <string>,
-    complete: <boolean>
+    "jsonrpc": "2.0",
+    "method": "notify_update_response",
+    "params": [
+        {
+            "application": "{app_name}",
+            "proc_id": 446461,
+            "message": "Update Response Message",
+            "complete": false
+        }
+    ]
 }
 ```
+The fields reported in the response are as follows:
+
 - The `application` field contains the name of application currently being
   updated.  Generally this will be either "moonraker", "klipper", "system",
   or "client".
@@ -1852,90 +2509,98 @@ Where `response` is an object int he following format:
   update, indicating that the update completed successfully.  Otherwise it
   will be false.
 
-### Update Manager Refreshed
+#### Update Manager Refreshed
 The update manager periodically auto refreshes the state of each application
 it is tracking.  After an auto refresh has completed the following
 notification is broadcast:
-
-`{jsonrpc: "2.0", method: "notify_update_refreshed", params: [update_info]}`
-
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "notify_update_refreshed",
+    "params": [{update_info}]}
+```
 Where `update_info` is an object that matches the response from an
 [update status](#get-update-status) request.
 
-### CPU Throttled
+#### CPU Throttled
 If the system supports throttled CPU monitoring Moonraker will send the
 following notification when it detectes an active throttled condition.
-
-`{jsonrpc: "2.0", method: "notify_cpu_throttled", params: [throttled_state]}`
-
-Where `throtled_state` is an object that matches the `throttled_state` in the
-response from a [process info](#get-process-info) request.  It is possible
-for clients to receive this notification multiple times if the system
-repeatedly transitions between an active and inactive throttled condition.
-
-### History Changed
-If the `[history]` module is enabled the following notification is sent when
-a job is added or finished:
-
-`{jsonrpc: "2.0", method: "notify_history_changed", params: [history_state]}`
-
-Where `history_state` is an object in the following format:
 ```json
 {
-    "action": "added" or "finished",
-    "job": <job object>
+    "jsonrpc": "2.0",
+    "method": "notify_cpu_throttled",
+    "params": [{throttled_state}]
 }
 ```
-The `job` field matches the object returned when requesting
+
+Where `throtled_state` is an object that matches the `throttled_state` field
+in the response from a [process info](#get-process-info) request.  It is
+possible for clients to receive this notification multiple times if the system
+repeatedly transitions between an active and inactive throttled condition.
+
+#### History Changed
+If the `[history]` module is enabled the following notification is sent when
+a job is added or finished:
+```json
+{
+    "jsonrpc": "2.0",
+    "method": "notify_history_changed",
+    "params": [
+        {
+            "action": "added",
+            "job": <job object>
+        }
+    ]
+}
+```
+The `action` field may be `added` or `finished`. The `job` field contains
+an object matches the one returned when requesting
 [job data](#get-a-single-job).
-# Appendix
 
-## Websocket setup
-All transmissions over the websocket are done via json using the JSON-RPC 2.0
-protocol.  While the websever expects a json encoded string, one limitation
-of Eventlet's websocket is that it can not send string encoded frames.  Thus
-the client will receive data om the server in the form of a binary Blob that
-must be read using a FileReader object then decoded.
+### Appendix
 
+#### Websocket setup
 The websocket is located at `ws://host:port/websocket`, for example:
 ```javascript
 var s = new WebSocket("ws://" + location.host + "/websocket");
 ```
 
-It also should be noted that if authorization is enabled, an untrusted client
-must request a "oneshot token" and add that token's value to the websocket's
-query string:
+**Note: A client using API Key authorization may request a
+[oneshot token](#generate-a-oneshot-token), applying the result to the
+websocket request's query string:**
 
+```http
+ws://host:port/websocket?token={32 character base32 string}
 ```
-ws://host:port/websocket?token=<32 character base32 string>
-```
-
-This is necessary as it isn't currently possible to add `X-Api-Key` to a
-Websocket object's request header.
 
 The following startup sequence is recommened for clients which make use of
 the websocket:
-1) Attempt to connect to `/websocket` until successful using a timer-like
-   mechanism
-2) Once connected, query `/printer/info` (or `printer.info`) for the ready
-   status.
-   - If the response returns an error (such as 404), set a timeout for
-     2 seconds and try again.
-   - If the response returns success, check the result's `state` attribute
-     - If `state == "ready"` you may proceed to request status of printer objects
-       make subscriptions, get the file list, etc.
-     - If `state == "error"` then Klippy has experienced an error
-       - If an error is detected it might be wise to prompt the user.  You can
-         get a description of the error from the `state_message` attribute
-     - If `state == "shutdown"` then Klippy is in a shutdown state.
-     - If `state == "startup"` then re-request printer info in 2s.
-- Repeat step 2 until Klipper reports ready.
-- Client's should watch for the `notify_klippy_disconnected` event.  If it reports
-  disconnected then Klippy has either been stopped or restarted.  In this
-  instance the client should repeat the steps above to determine when
-  klippy is ready.
 
-## Basic Print Status
+1. Attempt to connect to `/websocket` until successful using a timer-like
+   mechanism
+2. Once connected, query `/server/info` (or `server.info`) for the ready
+   status.
+      - If the response returns an error (such as 404) then either the client
+        is not authorized or Moonraker is not running.  Direct the user to
+        SSH into the machine and check `/tmp/moonraker.log`.
+      - If the response returns success, check the result's `klippy_state`
+        field:
+        - `klippy_state == "ready"`: you may proceed to request status of
+          printer objects make subscriptions, get the file list, etc.
+        - `klippy_state == "error"`:  Klippy has experienced an error
+          starting up
+        - `klippy_state == "shutdown"`: Klippy is in a shutdown state.
+        - `klippy_state == "startup"`: re-request `/server/info` in 2 seconds.
+             - If  `error` or `shutdown` is detected it might be wise to prompt
+               the user. You can get a description from the `state_message`
+               field of a `/printer/info` request.
+3. Repeat step 2 until Klipper reports ready.
+4. Clients should watch for the `notify_klippy_disconnected` event.  If
+   received then Klippy has either been stopped or restarted.  In this
+   state the client should repeat the steps above to determine when
+   klippy is ready.
+
+#### Basic Print Status
 An advanced client will likely use subscriptions and notifications
 to interact with Moonraker, however simple clients such as home automation
 software and embedded devices (ie: ESP32) may only wish to monitor the
@@ -1945,73 +2610,97 @@ via polling.
 - Set up a timer to poll at the desired interval.  Depending on your use
   case, 1 to 2 seconds is recommended.
 - On each cycle, issue the following request:
-  - `GET http://host/printer/objects/query?webhooks&virtual_sdcard&print_stats`\
-    Or via json-rpc:\
-    `{'jsonrpc': "2.0", 'method': "printer.objects.query", 'params':
-    {'objects': {'webhooks': null, 'virtual_sdcard': null,
-    'print_stats': null}}, id: <request id>}`
+
+        GET http://host/printer/objects/query?webhooks&virtual_sdcard&print_stats
+
+    Or via JSON-RPC 2.0:
+
+        {
+            "jsonrpc": "2.0",
+            "method": "printer.objects.query",
+            "params": {
+                "objects": {
+                    "webhooks": null,
+                    "virtual_sdcard": null,
+                    "print_stats": null
+                }
+            },
+            "id": 5664
+        }
+
 - If the request returns an error or the returned `result.status` is an empty
-  object printer objects are not available for query.  Each queried object
-  should be available in `result.status`.  The client should check to make
-  sure that all objects are received before proceeding.
-- Inspect `webhooks.ready`.  If the value is not "ready" the printer
+  object, then this is an indication that Klippy either experienced an error or
+  it is not properly configured.  Each queried object should be available in
+  `result.status`.  The client should check to make sure that all objects are
+  received before proceeding.
+- Inspect `webhooks.ready`.  If the value is not `ready` the printer
   is not available.  `webhooks.message` contains a message pertaining
   to the current state.
 - If the printer is ready, inspect `print_stats.state`.  It may be one
   of the following values:
-  - "standby": No print in progress
-  - "printing":  The printer is currently printing
-  - "paused":  A print in progress has been paused
-  - "error":  The print exited with an error.  `print_stats.message`
-    contains a related error message
-  - "complete":  The last print has completed
-- If `print_stats.state` is not "standby" then `print_stats.filename`
+      - `standby`: No print in progress
+      - `printing`:  The printer is currently printing
+      - `paused`:  A print in progress has been paused
+      - `error`:  The print exited with an error.  `print_stats.message`
+        contains a related error message
+      - `complete`:  The last print has completed
+- If `print_stats.state` is not `standby` then `print_stats.filename`
   will report the name of the currently loaded file.
 - `print_stats.filename` can be used to fetch file metadata.  It
-  is only necessary to fetch metadata once per print.\
-  `GET http://host/server/files/metadata?filename=<filename>`\
-  Or via json-rpc:\
-  `{jsonrpc: "2.0", method: "server.files.metadata",
-  params: {filename: "filename"}
-  , id: <request id>}`\
-  If metadata extraction failed then this request will return an error.
-  Some metadata fields are only populated for specific slicers, and
-  unsupported slicers will only return the size and modifed date.
+  is only necessary to fetch metadata once per print.
+
+        GET http://host/server/files/metadata?filename=<filename>
+
+    Or via JSON-RPC 2.0:
+
+        {
+            "jsonrpc": "2.0",
+            "method": "server.files.metadata",
+            "params": {
+                "filename": "{filename}"
+            },
+            "id": 5643
+        }
+
+    If metadata extraction failed then this request will return an error.
+    Some metadata fields are only populated for specific slicers, and
+    unsupported slicers will only return the size and modifed date.
+
 - There are multiple ways to calculate the ETA, this example will use
   file progress, as it is possible calculate the ETA with or without
   metadata.
-  - If `metadata.estimated_time` is available, the eta calculation can
-    be done as:
-    ```javascript
-    // assume "result" is the response from the status query
-    let vsd = result.status.virtual_sdcard;
-    let prog_time = vsd.progress * metadata.estimated_time;
-    let eta = metadata.estimated_time - prog_time
-    ```
-    Alternatively, one can simply subtract the print duration from
-    the estimated time:
-    ```javascript
-    // assume "result" is the response from the status query
-    let pstats = result.status.print_status;
-    let eta = metadata.estimated_time - pstats.print_duration;
-    if (eta < 0)
-      eta = 0;
-    ```
-  - If no metadata is available, print duration and progress can be used to
-    calculate the ETA:
-    ```javascript
-    // assume "result" is the response from the status query
-    let vsd = result.status.virtual_sdcard;
-    let pstats = result.status.print_stats;
-    let total_time = pstats.print_duration / vsd.progress;
-    let eta = total_time - pstats.print_duration;
-    ```
-- It is possible to query additional object if a client wishes to display
-  more information (ie: temperatures).  See
-  [printer_objects.md](printer_objects.md) for more information.
+    - If `metadata.estimated_time` is available, the eta calculation can
+      be done as:
 
-## Bed Mesh Coordinates
-The [bed_mesh](printer_objects.md#bed_mesh) printer object may be used
+            // assume "result" is the response from the status query
+            let vsd = result.status.virtual_sdcard;
+            let prog_time = vsd.progress * metadata.estimated_time;
+            let eta = metadata.estimated_time - prog_time
+
+        Alternatively, one can simply subtract the print duration from
+        the estimated time:
+
+            // assume "result" is the response from the status query
+            let pstats = result.status.print_status;
+            let eta = metadata.estimated_time - pstats.print_duration;
+            if (eta < 0)
+            eta = 0;
+
+    - If no metadata is available, print duration and progress can be used to
+      calculate the ETA:
+
+            // assume "result" is the response from the status query
+            let vsd = result.status.virtual_sdcard;
+            let pstats = result.status.print_stats;
+            let total_time = pstats.print_duration / vsd.progress;
+            let eta = total_time - pstats.print_duration;
+
+- It is possible to query additional objects if a client wishes to display
+  more information (ie: temperatures).  See the
+  [Printer Objects](printer_objects.md) documentation for details.
+
+#### Bed Mesh Coordinates
+The [Bed Mesh](printer_objects.md#bed_mesh) printer object may be used
 to generate three dimensional coordinates of a probed area (or mesh).
 Below is an example (in javascript) of how to transform the data received
 from a bed_mesh object query into an array of 3D coordinates.
@@ -2047,3 +2736,15 @@ function process_mesh(result) {
 // Use the array of coordinates visualize the probed area
 // or mesh..
 ```
+
+#### Converting to Unix Time
+Some of Moonraker's APIs return a date represented in Unix time.
+Most languanges have functionality built in to convert Unix
+time to a workable object or string.  For example, in JavaScript
+one might do something like the following:
+```javascript
+for (let resp of result.gcode_store) {
+  let date = new Date(resp.time * 1000);
+  // Do something with date and resp.message ...
+}
+```
diff --git a/mkdocs.yml b/mkdocs.yml
new file mode 100644
index 0000000..d1535ae
--- /dev/null
+++ b/mkdocs.yml
@@ -0,0 +1,19 @@
+site_name: Moonraker
+repo_url: https://github.com/Arksine/moonraker
+nav:
+    - 'User Documentation':
+        - Installation: installation.md
+        - Configuration : configuration.md
+        - User Changes: user_changes.md
+    - 'Client Developers':
+        - Client API: web_api.md
+        - Printer Objects: printer_objects.md
+        - API Changes: api_changes.md
+    - 'Backend Developers':
+        - Contributing: contributing.md
+theme:
+    name: readthedocs
+plugins:
+    - search
+markdown_extensions:
+    - codehilite