Skip to content

Start an interactive pairing request

POST
/api/plugins/pairing
curl --request POST \
--url https://example.com/api/plugins/pairing \
--header 'Content-Type: application/json' \
--data '{ "pluginId": "example", "displayName": "example", "codeChallenge": "example", "codeChallengeMethod": "example", "client": { "executablePath": "example", "processId": 1, "sdkVersion": "example" } }'

A plugin creates a short-lived PKCE-style pairing request over loopback, unauthenticated - the request itself is what a plugin has instead of a credential. The desktop UI approves or rejects it out of band; the plugin polls GET …/{requestId} and then redeems it.

Media typeapplication/json

Mirrors MacroDeck.Plugin.Protocol.Handshake.PluginPairingRequest.

object
pluginId
required

Reverse-domain package id, validated with MacroDeckId.IsValidOwnerId.

string
displayName
required
string
codeChallenge
required

PKCE code challenge, derived from the plugin’s code verifier.

string
codeChallengeMethod
required

Mirrors MacroDeck.Plugin.Protocol.Handshake.PluginPairingChallengeMethods. Currently only “S256”.

string
client

Mirrors MacroDeck.Plugin.Protocol.Handshake.PluginPairingClientInfo. Every field is self-reported by the requesting process and is not verified by the host.

object
executablePath
string
nullable
processId
integer
nullable
sdkVersion
string
nullable
Examplegenerated
{
"pluginId": "example",
"displayName": "example",
"codeChallenge": "example",
"codeChallengeMethod": "example",
"client": {
"executablePath": "example",
"processId": 1,
"sdkVersion": "example"
}
}

The pairing request was created.

Media typeapplication/json

Mirrors MacroDeck.Plugin.Protocol.Handshake.PluginPairingResponse.

object
requestId
required

Identifies the request but is not a credential and authorises nothing - it is safe in a URL, and the verifier is what proves possession.

string
expiresAt
required
string format: date-time
pollIntervalSeconds
required
integer
Examplegenerated
{
"requestId": "example",
"expiresAt": "2026-04-15T12:00:00Z",
"pollIntervalSeconds": 1
}

The request body does not match the expected shape.

Media typeapplication/json

Mirrors MacroDeck.Plugin.Protocol.Errors.ProtocolError.

object
code
required

One of the codes v1 speaks. Drift anchor - see x-macrodeck-error-codes above.

string
Allowed values: PROTOCOL_VERSION_UNSUPPORTED UNKNOWN_MESSAGE_TYPE MALFORMED_ENVELOPE INVALID_PAYLOAD UNAUTHENTICATED PLUGIN_ALREADY_REGISTERED SESSION_EXPIRED SESSION_NOT_RESUMABLE SESSION_REPLACED SESSION_NOT_FOUND CAPABILITY_UNSUPPORTED CAPABILITY_UNAVAILABLE PAYLOAD_TOO_LARGE ASSET_TOO_LARGE QUEUE_OVERFLOW RATE_LIMITED TIMEOUT CANCELLED CORRELATION_UNKNOWN DUPLICATE_IDEMPOTENCY_KEY INTERNAL_ERROR
message
required

Default English text keyed by code (MacroDeck.Plugin.Protocol.Errors.ProtocolErrorMessages). The UI localises from the code, not this string.

string
details
object
key
additional properties
string
retryable
required
boolean
Example
{
"code": "PROTOCOL_VERSION_UNSUPPORTED"
}

The caller did not arrive on a loopback remote address. Plugin endpoints are served on both the public and private listeners, but only local processes may reach them. On the pairing endpoints, on POST /api/plugins/registration, and on POST /api/plugins/sessions for a plugin holding a development credential, this status also means Developer Mode is switched off in the desktop app - such a refusal carries “reason”: “developer_mode_disabled” in the error’s details, and GET /api/plugins/protocol reports the switch ahead of time.

Media typeapplication/json

Mirrors MacroDeck.Plugin.Protocol.Errors.ProtocolError.

object
code
required

One of the codes v1 speaks. Drift anchor - see x-macrodeck-error-codes above.

string
Allowed values: PROTOCOL_VERSION_UNSUPPORTED UNKNOWN_MESSAGE_TYPE MALFORMED_ENVELOPE INVALID_PAYLOAD UNAUTHENTICATED PLUGIN_ALREADY_REGISTERED SESSION_EXPIRED SESSION_NOT_RESUMABLE SESSION_REPLACED SESSION_NOT_FOUND CAPABILITY_UNSUPPORTED CAPABILITY_UNAVAILABLE PAYLOAD_TOO_LARGE ASSET_TOO_LARGE QUEUE_OVERFLOW RATE_LIMITED TIMEOUT CANCELLED CORRELATION_UNKNOWN DUPLICATE_IDEMPOTENCY_KEY INTERNAL_ERROR
message
required

Default English text keyed by code (MacroDeck.Plugin.Protocol.Errors.ProtocolErrorMessages). The UI localises from the code, not this string.

string
details
object
key
additional properties
string
retryable
required
boolean
Example
{
"code": "PROTOCOL_VERSION_UNSUPPORTED"
}

A plugin is already registered with this identity.

Media typeapplication/json

Mirrors MacroDeck.Plugin.Protocol.Errors.ProtocolError.

object
code
required

One of the codes v1 speaks. Drift anchor - see x-macrodeck-error-codes above.

string
Allowed values: PROTOCOL_VERSION_UNSUPPORTED UNKNOWN_MESSAGE_TYPE MALFORMED_ENVELOPE INVALID_PAYLOAD UNAUTHENTICATED PLUGIN_ALREADY_REGISTERED SESSION_EXPIRED SESSION_NOT_RESUMABLE SESSION_REPLACED SESSION_NOT_FOUND CAPABILITY_UNSUPPORTED CAPABILITY_UNAVAILABLE PAYLOAD_TOO_LARGE ASSET_TOO_LARGE QUEUE_OVERFLOW RATE_LIMITED TIMEOUT CANCELLED CORRELATION_UNKNOWN DUPLICATE_IDEMPOTENCY_KEY INTERNAL_ERROR
message
required

Default English text keyed by code (MacroDeck.Plugin.Protocol.Errors.ProtocolErrorMessages). The UI localises from the code, not this string.

string
details
object
key
additional properties
string
retryable
required
boolean
Example
{
"code": "PROTOCOL_VERSION_UNSUPPORTED"
}

Too many requests.

Media typeapplication/json

Mirrors MacroDeck.Plugin.Protocol.Errors.ProtocolError.

object
code
required

One of the codes v1 speaks. Drift anchor - see x-macrodeck-error-codes above.

string
Allowed values: PROTOCOL_VERSION_UNSUPPORTED UNKNOWN_MESSAGE_TYPE MALFORMED_ENVELOPE INVALID_PAYLOAD UNAUTHENTICATED PLUGIN_ALREADY_REGISTERED SESSION_EXPIRED SESSION_NOT_RESUMABLE SESSION_REPLACED SESSION_NOT_FOUND CAPABILITY_UNSUPPORTED CAPABILITY_UNAVAILABLE PAYLOAD_TOO_LARGE ASSET_TOO_LARGE QUEUE_OVERFLOW RATE_LIMITED TIMEOUT CANCELLED CORRELATION_UNKNOWN DUPLICATE_IDEMPOTENCY_KEY INTERNAL_ERROR
message
required

Default English text keyed by code (MacroDeck.Plugin.Protocol.Errors.ProtocolErrorMessages). The UI localises from the code, not this string.

string
details
object
key
additional properties
string
retryable
required
boolean
Example
{
"code": "PROTOCOL_VERSION_UNSUPPORTED"
}
Retry-After
required
integer format: int32