diff --git a/data/xap/xap_0.3.1.hjson b/data/xap/xap_0.3.1.hjson
new file mode 100644
index 00000000000..ab6dc34c50d
--- /dev/null
+++ b/data/xap/xap_0.3.1.hjson
@@ -0,0 +1,21 @@
+{
+ version: 0.3.1
+ routes: {
+ 0x01: {
+ routes: {
+ 0x10: {
+ type: command
+ name: Keycodes version query
+ define: KEYCODES_VERSION_QUERY
+ description:
+ '''
+ Query the version of the keycodes known by the firmware.
+ '''
+ return_type: u32
+ return_purpose: bcd-version
+ return_constant: QMK_KEYCODES_VERSION_BCD
+ }
+ }
+ }
+ }
+}
diff --git a/docs/xap_0.3.1.md b/docs/xap_0.3.1.md
new file mode 100644
index 00000000000..16ccc64d4de
--- /dev/null
+++ b/docs/xap_0.3.1.md
@@ -0,0 +1,252 @@
+
+
+
+
+# QMK Firmware XAP Specs
+
+This document describes the requirements of the QMK XAP ("extensible application protocol") API.
+
+## Types
+
+**All integral types are little-endian.**
+
+| Name | Definition |
+| -- | -- |
+| _bool_ | Data type that contains values 0 and 1. Implemented as an alias of `u8`. |
+| _struct{}_ | A structure of data, packing different objects together. Data is "compacted" -- there are no padding bytes between fields. Equivalent to a packed C-style `struct`. The order in which they're defined matches the order of the data in the response packet. |
+| _type[n]_ | An array of `type`, with array extent of `N` -- e.g. `u8[2]` signifies two consecutive octets. |
+| _u16_ | An unsigned 16-bit integral, commonly seen as `uint16_t` from _stdint.h_. |
+| _u32_ | An unsigned 32-bit integral, commonly seen as `uint32_t` from _stdint.h_. |
+| _u64_ | An unsigned 64-bit integral, commonly seen as `uint64_t` from _stdint.h_. |
+| _u8_ | An unsigned 8-bit integral (octet, or byte), commonly seen as `uint8_t` from _stdint.h_. |
+
+## Definitions
+
+This list defines the terms used across the entire set of XAP protocol documentation.
+
+| Name | Definition |
+| -- | -- |
+| _Capability_ | A way to determine if certain functionality is enabled in the firmware. Any _subsystem_ that provides build-time restriction of functionality must provide a _route_ for a _capabilities query_. |
+| _Handler_ | A piece of code that is executed when a specific _route_ is received. |
+| _Payload_ | Any received data appended to the _route_, which gets delivered to the _handler_ when received. |
+| _Response_ | The data sent back to the host during execution of a _handler_. |
+| _Route_ | A sequence of _IDs_ describing the route to invoke a _handler_. |
+| _Secure Route_ | A _route_ which has potentially destructive consequences, necessitating prior approval by the user before executing. |
+| _Subsystem_ | A high-level area of functionality within XAP. |
+| _Unlock sequence_ | A physical sequence initiated by the user to enable execution of _secure routes_. |
+| _Broadcast Header_ | Packet format for broadcast messages. Takes the format:
`token` - token
`u8` - type
`u8` - length |
+| _ID_ | A single octet / 8-bit byte, representing Subsystem or Route index. |
+| _Request Header_ | Packet format for inbound data. Takes the format:
`token` - token
`u8` - length |
+| _Response Flags_ | An `u8` containing the status of the request. |
+| _Response Header_ | Packet format for outbound data. Takes the format:
`token` - token
`response_flags` - flags
`u8` - length |
+| _Token_ | A `u16` associated with a specific request as well as its corresponding response. Valid token values are within the range `0x0100`-`0xFFFF`. |
+
+## Requests and Responses
+
+Communication generally follows a request/response pattern.
+
+Each request needs to include a _token_ -- this `u16` value prefixes each outbound request from the host application and its corresponding response.
+This allows response messages to be correlated with their request, even if multiple host applications are communicating with the firmware simultaneously.
+Host applications should randomly generate a token ID for **every** outbound request, unless using a reserved token defined below.
+To ensure host interoperability, valid token values are within the range `0x0100`-`0xFFFF`.
+
+This token is followed by a `u8` signifying the length of data in the request.
+
+Two token values are reserved: `0xFFFE` and `0xFFFF`:
+* `0xFFFE`: A message sent by a host application may use this token if no response is to be sent -- a "fire and forget" message.
+* `0xFFFF`: Signifies a "broadcast" message sent by the firmware without prompting from the host application. Broadcast messages are defined later in this document.
+
+When not using reserved tokens above, requests will always generate one corresponding response with a matching token.
+
+Maximum total message length is 64 bytes - a single HID report size, no fragmentation or reassembly needed.
+
+Response messages will always be prefixed by the originating request _token_, directly followed by that request's _response flags_, then the response payload length:
+
+| Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
+| -- | -- | -- | -- | -- | -- | -- | -- |
+| `-` | `-` | `-` | `-` | `-` | `-` | `SECURE_FAILURE` | `SUCCESS` |
+
+* Bit 1 (`SECURE_FAILURE`): When this bit is set, the requested _route_ was marked _secure_ but an _unlock sequence_ has not completed.
+* Bit 0 (`SUCCESS`): When this bit is set, the request was successfully handled. If not set, all payload data should be disregarded, and the request retried if appropriate (with a new token).
+
+### Example "conversation":
+
+**Request** -- version query:
+
+| Byte | 0 | 1 | 2 | 3 | 4 |
+| --- | --- | --- | --- | --- | --- |
+| **Purpose** | Token | Token | Payload Length | Route | Route |
+| **Value** | `0x43` | `0x2B` | `0x02` | `0x00` | `0x00` |
+
+**Response** -- matching token, successful flag, payload of `0x03170192` = 3.17.192:
+
+| Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
+| --- | --- | --- | --- | --- | --- | --- | --- | --- |
+| **Purpose** | Token | Token | Response Flags | Payload Length | Payload | Payload | Payload | Payload |
+| **Value** | `0x43` | `0x2B` | `0x01` | `0x04` | `0x92` | `0x01` | `0x17` | `0x03` |
+
+## Routes
+
+Subsystem validity should be queried through the “Enabled-in-firmware subsystem query” under the QMK subsystem (route=0x00,0x01).
+This is the primary method for determining if a subsystem has been enabled in the running firmware.
+
+### XAP - `0x00`
+This subsystem is always present, and provides the ability to query information about the XAP protocol of the connected device.
+
+| Name | Route | Tags | Payloads | Description |
+| -- | -- | -- | -- | -- |
+| Version Query | `0x00 0x00` | | __Response:__ `u32` | XAP protocol version query.
* Returns the BCD-encoded version in the format of XX.YY.ZZZZ => `0xXXYYZZZZ`
* e.g. 3.2.115 will match `0x03020115`, or bytes {0x15,0x01,0x02,0x03}.|
+| Capabilities Query | `0x00 0x01` | | __Response:__ `u32` | XAP subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.|
+| Enabled subsystem query | `0x00 0x02` | | __Response:__ `u32` | XAP protocol subsystem query. Each bit should be considered as a "usable" subsystem. For example, checking `(value & (1 << XAP_ROUTE_QMK) != 0)` means the QMK subsystem is enabled and available for querying.|
+| Secure Status | `0x00 0x03` | | __Response:__ `u8` | Query secure route status
* 0 means secure routes are disabled
* 1 means unlock sequence initiated but incomplete
* 2 means secure routes are allowed
* any other value should be interpreted as disabled|
+| Secure Unlock | `0x00 0x04` | | | Initiate secure route unlock sequence|
+| Secure Lock | `0x00 0x05` | | | Disable secure routes|
+
+### QMK - `0x01`
+This subsystem is always present, and provides the ability to address QMK-specific functionality.
+
+| Name | Route | Tags | Payloads | Description |
+| -- | -- | -- | -- | -- |
+| Version Query | `0x01 0x00` | | __Response:__ `u32` | QMK protocol version query.
* Returns the BCD-encoded version in the format of XX.YY.ZZZZ => `0xXXYYZZZZ`
* e.g. 3.2.115 will match `0x03020115`, or bytes {0x15,0x01,0x02,0x03}.|
+| Capabilities Query | `0x01 0x01` | | __Response:__ `u32` | QMK subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.|
+| Board identifiers | `0x01 0x02` | | __Response:__
* Vendor ID: `u16`
* Product ID: `u16`
* Product Version: `u16`
* QMK Unique Identifier: `u32` | Retrieves the set of identifying information for the board.|
+| Board Manufacturer | `0x01 0x03` | | __Response:__ `string` | Retrieves the name of the manufacturer|
+| Product Name | `0x01 0x04` | | __Response:__ `string` | Retrieves the product name|
+| Config Blob Length | `0x01 0x05` | | __Response:__ `u16` | Retrieves the length of the configuration data bundled within the firmware|
+| Config Blob Chunk | `0x01 0x06` | | __Request:__
* offset: `u16`
__Response:__ `u8[32]` | Retrieves a chunk of the configuration data bundled within the firmware|
+| Jump to bootloader | `0x01 0x07` | __Secure__ | __Response:__ `u8` | Jump to bootloader
May not be present - if QMK capabilities query returns “true”, then jump to bootloader is supported
* 0 means secure routes are disabled, and should be considered as a failure
* 1 means successful, board will jump to bootloader|
+| Hardware Identifier | `0x01 0x08` | | __Response:__ `u32[4]` | Retrieves a unique identifier for the board.|
+| Reinitialize EEPROM | `0x01 0x09` | __Secure__ | __Response:__ `u8` | Reinitializes the keyboard's EEPROM (persistent memory)
May not be present - if QMK capabilities query returns “true”, then reinitialize is supported
* 0 means secure routes are disabled, and should be considered as a failure
* 1 means successful, board will reinitialize and then reboot|
+| Keycodes version query | `0x01 0x10` | | __Response:__ `u32` | Query the version of the keycodes known by the firmware.|
+
+### Keyboard - `0x02`
+This subsystem is always present, and reserved for vendor-specific functionality. No routes are defined by XAP.
+
+### User - `0x03`
+This subsystem is always present, and reserved for user-specific functionality. No routes are defined by XAP.
+
+### Keymap - `0x04`
+This subsystem allows for query of currently configured keycodes.
+
+| Name | Route | Tags | Payloads | Description |
+| -- | -- | -- | -- | -- |
+| Capabilities Query | `0x04 0x01` | | __Response:__ `u32` | Keymap subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.|
+| Get Layer Count | `0x04 0x02` | | __Response:__ `u8` | Query maximum number of layers that can be addressed within the keymap.|
+| Get Keycode | `0x04 0x03` | | __Request:__
* Layer: `u8`
* Row: `u8`
* Column: `u8`
__Response:__ `u16` | Query the Keycode at the requested location.|
+| Get Encoder Keycode | `0x04 0x04` | | __Request:__
* Layer: `u8`
* Encoder: `u8`
* Clockwise: `u8`
__Response:__ `u16` | Query the Keycode at the requested location.|
+
+### Remapping - `0x05`
+This subsystem allows for live reassignment of keycodes without rebuilding the firmware.
+
+| Name | Route | Tags | Payloads | Description |
+| -- | -- | -- | -- | -- |
+| Capabilities Query | `0x05 0x01` | | __Response:__ `u32` | Remapping subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.|
+| Get Layer Count | `0x05 0x02` | | __Response:__ `u8` | Query maximum number of layers that can be addressed within the keymap.|
+| Set Keycode | `0x05 0x03` | __Secure__ | __Request:__
* Layer: `u8`
* Row: `u8`
* Column: `u8`
* Keycode: `u16` | Modify the Keycode at the requested location.|
+| Set Encoder Keycode | `0x05 0x04` | __Secure__ | __Request:__
* Layer: `u8`
* Encoder: `u8`
* Clockwise: `u8`
* Keycode: `u16` | Modify the Keycode at the requested location.|
+
+### Lighting - `0x06`
+This subsystem allows for control over the lighting subsystem.
+
+| Name | Route | Tags | Payloads | Description |
+| -- | -- | -- | -- | -- |
+| Capabilities Query | `0x06 0x01` | | __Response:__ `u32` | Lighting subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.|
+
+
+#### backlight - `0x06 0x02`
+This subsystem allows for control over the backlight subsystem.
+
+| Name | Route | Tags | Payloads | Description |
+| -- | -- | -- | -- | -- |
+| Capabilities Query | `0x06 0x02 0x01` | | __Response:__ `u32` | backlight subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.|
+| Get Enabled Effects | `0x06 0x02 0x02` | | __Response:__ `u8` | Each bit should be considered as a "usable" effect id|
+| Get Config | `0x06 0x02 0x03` | | __Response:__
* enable: `u8`
* mode: `u8`
* val: `u8` | Query the current config.|
+| Set Config | `0x06 0x02 0x04` | | __Request:__
* enable: `u8`
* mode: `u8`
* val: `u8` | Set the current config.|
+| Save Config | `0x06 0x02 0x05` | | | Save the current config.|
+
+
+#### rgblight - `0x06 0x03`
+This subsystem allows for control over the rgblight subsystem.
+
+| Name | Route | Tags | Payloads | Description |
+| -- | -- | -- | -- | -- |
+| Capabilities Query | `0x06 0x03 0x01` | | __Response:__ `u32` | rgblight subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.|
+| Get Enabled Effects | `0x06 0x03 0x02` | | __Response:__ `u64` | Each bit should be considered as a "usable" effect id|
+| Get Config | `0x06 0x03 0x03` | | __Response:__
* enable: `u8`
* mode: `u8`
* hue: `u8`
* sat: `u8`
* val: `u8`
* speed: `u8` | Query the current config.|
+| Set Config | `0x06 0x03 0x04` | | __Request:__
* enable: `u8`
* mode: `u8`
* hue: `u8`
* sat: `u8`
* val: `u8`
* speed: `u8` | Set the current config.|
+| Save Config | `0x06 0x03 0x05` | | | Save the current config.|
+
+
+#### rgbmatrix - `0x06 0x04`
+This subsystem allows for control over the rgb matrix subsystem.
+
+| Name | Route | Tags | Payloads | Description |
+| -- | -- | -- | -- | -- |
+| Capabilities Query | `0x06 0x04 0x01` | | __Response:__ `u32` | rgb matrix subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.|
+| Get Enabled Effects | `0x06 0x04 0x02` | | __Response:__ `u64` | Each bit should be considered as a "usable" effect id|
+| Get Config | `0x06 0x04 0x03` | | __Response:__
* enable: `u8`
* mode: `u8`
* hue: `u8`
* sat: `u8`
* val: `u8`
* speed: `u8`
* flags: `u8` | Query the current config.|
+| Set Config | `0x06 0x04 0x04` | | __Request:__
* enable: `u8`
* mode: `u8`
* hue: `u8`
* sat: `u8`
* val: `u8`
* speed: `u8`
* flags: `u8` | Set the current config.|
+| Save Config | `0x06 0x04 0x05` | | | Save the current config.|
+
+### Audio - `0x07`
+ This subsystem allows for control over the audio subsystem.
+
+| Name | Route | Tags | Payloads | Description |
+| -- | -- | -- | -- | -- |
+| Capabilities Query | `0x07 0x01` | | __Response:__ `u32` | Audio subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.|
+| Get Config | `0x07 0x03` | | __Response:__
* enable: `u8`
* clicky_enable: `u8` | Query the current config.|
+| Set Config | `0x07 0x04` | | __Request:__
* enable: `u8`
* clicky_enable: `u8` | Set the current config.|
+| Save Config | `0x07 0x05` | | | Save the current config.|
+
+
+## Broadcast messages
+
+Broadcast messages may be sent by the firmware to the host, without a corresponding inbound request. Each broadcast message uses the token `0xFFFF`, and does not expect a response from the host. Tokens are followed by an _ID_ signifying the type of broadcast, then the response _payload_ length, and finally the corresponding _payload_.
+
+### Log message - `0x00`
+Replicates and replaces the same functionality as if using the standard QMK `CONSOLE_ENABLE = yes` in `rules.mk`. Normal prints within the firmware will manifest as log messages broadcast to the host. `hid_listen` will not be functional with XAP enabled.
+
+Log message payloads include `u8[Length]` containing the text, where the length of the text is the _broadcast_header.length_ field.
+
+**Example Log Broadcast** -- log message "Hello QMK!"
+
+| Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
+| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
+| **Purpose** | Token | Token | Broadcast Type | Length | Payload | Payload | Payload | Payload | Payload | Payload | Payload | Payload | Payload | Payload |
+| **Value** | `0xFF` | `0xFF` | `0x00` | `0x0A`(10) | `0x48`(H) | `0x65`(e) | `0x6C`(l) | `0x6C`(l) | `0x6F`(o) | `0x20`( ) | `0x51`(Q) | `0x4D`(M) | `0x4B`(K) | `0x21`(!) |
+### Secure Status - `0x01`
+Secure status has changed. Payloads include a `u8` matching a 'Secure Status' request.
+
+**Example Secure Status Broadcast** -- secure "Unlocking"
+
+| Byte | 0 | 1 | 2 | 3 | 4 |
+| --- | --- | --- | --- | --- | --- |
+| **Purpose** | Token | Token | Broadcast Type | Length | Secure Status |
+| **Value** | `0xFF` | `0xFF` | `0x01` | `0x01` | `0x01` |
+### Keyboard - `0x02`
+Reserved for vendor-specific functionality. No messages are defined by XAP.
+### User - `0x03`
+Reserved for user-specific functionality. No messages are defined by XAP.
+
diff --git a/docs/xap_protocol.md b/docs/xap_protocol.md
index 2d37b9c3b10..f2f6d336921 100644
--- a/docs/xap_protocol.md
+++ b/docs/xap_protocol.md
@@ -24,6 +24,7 @@
"Y88P"
*******************************************************************************
--->
+* [XAP Version 0.3.1](xap_0.3.1.md)
* [XAP Version 0.3.0](xap_0.3.0.md)
* [XAP Version 0.2.0](xap_0.2.0.md)
* [XAP Version 0.1.0](xap_0.1.0.md)