{
  "openapi": "3.1.0",
  "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
  "info": {
    "title": "Feel My Mac Local API",
    "version": "1.0.0",
    "description": "Protocol v1, available in Feel My Mac 1.2.0 or later. Purchased/activated Feel My Mac license required. No additional API usage fee for experimentation/nonprofit use; contact business@thehighjack.it to integrate in a game or program. Any local process can submit requests while Local API is selected. Client UUIDs are not credentials. Material audio is disabled; physical actuator frequency is not exposed.",
    "contact": {
      "email": "business@thehighjack.it",
      "url": "https://feelmymac.thehighjack.it/developers"
    }
  },
  "servers": [
    {
      "url": "http://127.0.0.1:59723",
      "description": "Local macOS runtime only; not a browser or cloud endpoint."
    }
  ],
  "security": [],
  "externalDocs": {
    "url": "https://feelmymac.thehighjack.it/developers",
    "description": "API integration guide and separate native tester"
  },
  "paths": {
    "/v1/status": {
      "get": {
        "operationId": "getStatus",
        "summary": "Read availability and effect state",
        "description": "No haptic output. No client UUID required. The listener exists only while Local API is selected.",
        "responses": {
          "200": {
            "description": "Command accepted or current state. Acceptance does not guarantee a physical sensation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Status"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          },
          "400": {
            "description": "invalid_client, invalid_parameters, bad_request or incomplete_request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "invalid_host or browser_origin_denied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "request_too_large or body_too_large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "431": {
            "description": "headers_too_large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "mode_inactive, unavailable, output_unavailable or gesture_unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/parameters": {
      "get": {
        "operationId": "getParameters",
        "summary": "Discover supported controls",
        "description": "No haptic output. Detect controlled resonance and field support here before starting.",
        "responses": {
          "200": {
            "description": "Command accepted or current state. Acceptance does not guarantee a physical sensation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Parameters"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          },
          "400": {
            "description": "invalid_client, invalid_parameters, bad_request or incomplete_request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "invalid_host or browser_origin_denied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "request_too_large or body_too_large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "431": {
            "description": "headers_too_large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "mode_inactive, unavailable, output_unavailable or gesture_unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/impulses": {
      "post": {
        "operationId": "emitImpulse",
        "summary": "Emit a single impact",
        "description": "Replaces the same owner’s current effect. Retains no ownership after completion. Do not automatically retry after an ambiguous network timeout.",
        "responses": {
          "200": {
            "description": "Command accepted or current state. Acceptance does not guarantee a physical sensation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImpulseResponse"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          },
          "400": {
            "description": "invalid_client, invalid_parameters, bad_request or incomplete_request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "invalid_host or browser_origin_denied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "busy, no_resonance, no_gesture or no_controlled_resonance, depending on endpoint/state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "411": {
            "description": "length_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "request_too_large or body_too_large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "json_required or encoding_not_supported.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "417": {
            "description": "expect_not_supported.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "431": {
            "description": "headers_too_large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "mode_inactive, unavailable, output_unavailable or gesture_unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientID"
          },
          {
            "$ref": "#/components/parameters/ClientName"
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Send JSON with exact Content-Length; no chunking, compression, Origin or Sec-Fetch-* headers.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Impulse"
              },
              "examples": {
                "natural": {
                  "value": {
                    "style": "natural",
                    "intensity": 0.25
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/resonance": {
      "post": {
        "operationId": "startResonance",
        "summary": "Start or replace resonance",
        "description": "Mode must be gesture, timed or controlled. Unknown fields and null are rejected. Gesture/controlled require renewals, expiring after 2000 ms without renewal; recommended heartbeat 500 ms. Controlled does not require movement. Timed lasts only its original bounded duration.",
        "responses": {
          "200": {
            "description": "Command accepted or current state. Acceptance does not guarantee a physical sensation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResonanceState"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          },
          "400": {
            "description": "invalid_client, invalid_parameters, bad_request or incomplete_request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "invalid_host or browser_origin_denied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "busy, no_resonance, no_gesture or no_controlled_resonance, depending on endpoint/state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "411": {
            "description": "length_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "request_too_large or body_too_large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "json_required or encoding_not_supported.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "417": {
            "description": "expect_not_supported.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "431": {
            "description": "headers_too_large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "mode_inactive, unavailable, output_unavailable or gesture_unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientID"
          },
          {
            "$ref": "#/components/parameters/ClientName"
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Send JSON with exact Content-Length; no chunking, compression, Origin or Sec-Fetch-* headers.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ResonanceStart"
              },
              "examples": {
                "gesture": {
                  "value": {
                    "mode": "gesture",
                    "intensity": 0.4,
                    "fineness": 0.5,
                    "reactivity": 0.5
                  }
                },
                "timed": {
                  "value": {
                    "mode": "timed",
                    "intensity": 0.4,
                    "fineness": 0.5,
                    "durationMs": 300
                  }
                },
                "controlled": {
                  "value": {
                    "mode": "controlled",
                    "intensity": 0.1,
                    "pulseRateHz": 6,
                    "sharpness": 0,
                    "transitionMs": 60
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/resonance/update": {
      "post": {
        "operationId": "updateResonance",
        "summary": "Edit active resonance",
        "description": "Partial nonempty update; fields must match the active mode. Omitted fields remain unchanged. Cannot change mode or restart an expired session. Gesture/controlled edits renew the lease. Timed duration remains relative to original start; shortening below elapsed time ends the effect.",
        "responses": {
          "200": {
            "description": "Command accepted or current state. Acceptance does not guarantee a physical sensation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResonanceState"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          },
          "400": {
            "description": "invalid_client, invalid_parameters, bad_request or incomplete_request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "invalid_host or browser_origin_denied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "busy, no_resonance, no_gesture or no_controlled_resonance, depending on endpoint/state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "411": {
            "description": "length_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "request_too_large or body_too_large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "json_required or encoding_not_supported.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "417": {
            "description": "expect_not_supported.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "431": {
            "description": "headers_too_large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "mode_inactive, unavailable, output_unavailable or gesture_unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientID"
          },
          {
            "$ref": "#/components/parameters/ClientName"
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Send JSON with exact Content-Length; no chunking, compression, Origin or Sec-Fetch-* headers.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ResonanceUpdate"
              },
              "examples": {
                "controlled": {
                  "value": {
                    "intensity": 0.65,
                    "pulseRateHz": 40,
                    "sharpness": 0.8
                  }
                },
                "gesture": {
                  "value": {
                    "fineness": 0.8
                  }
                },
                "timed": {
                  "value": {
                    "durationMs": 800
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/resonance/pause": {
      "post": {
        "operationId": "pauseResonance",
        "summary": "Pause controlled resonance",
        "description": "Controlled mode only. Idempotent; preserves owner and parameters and renews lease. Continue heartbeat while paused. Transitions advance with wall time during pause; resume uses current values, without catch-up bursts.",
        "responses": {
          "200": {
            "description": "Command accepted or current state. Acceptance does not guarantee a physical sensation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResonanceState"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          },
          "400": {
            "description": "invalid_client, invalid_parameters, bad_request or incomplete_request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "invalid_host or browser_origin_denied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "busy, no_resonance, no_gesture or no_controlled_resonance, depending on endpoint/state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "411": {
            "description": "length_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "request_too_large or body_too_large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "json_required or encoding_not_supported.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "417": {
            "description": "expect_not_supported.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "431": {
            "description": "headers_too_large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "mode_inactive, unavailable, output_unavailable or gesture_unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientID"
          },
          {
            "$ref": "#/components/parameters/ClientName"
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Send JSON with exact Content-Length; no chunking, compression, Origin or Sec-Fetch-* headers.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmptyCommand"
              },
              "examples": {
                "empty": {
                  "value": {}
                }
              }
            }
          }
        }
      }
    },
    "/v1/resonance/resume": {
      "post": {
        "operationId": "resumeResonance",
        "summary": "Resume controlled resonance",
        "description": "Controlled mode only. Idempotent; preserves owner and parameters and renews lease. Continue heartbeat while paused. Transitions advance with wall time during pause; resume uses current values, without catch-up bursts.",
        "responses": {
          "200": {
            "description": "Command accepted or current state. Acceptance does not guarantee a physical sensation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResonanceState"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          },
          "400": {
            "description": "invalid_client, invalid_parameters, bad_request or incomplete_request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "invalid_host or browser_origin_denied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "busy, no_resonance, no_gesture or no_controlled_resonance, depending on endpoint/state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "411": {
            "description": "length_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "request_too_large or body_too_large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "json_required or encoding_not_supported.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "417": {
            "description": "expect_not_supported.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "431": {
            "description": "headers_too_large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "mode_inactive, unavailable, output_unavailable or gesture_unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientID"
          },
          {
            "$ref": "#/components/parameters/ClientName"
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Send JSON with exact Content-Length; no chunking, compression, Origin or Sec-Fetch-* headers.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmptyCommand"
              },
              "examples": {
                "empty": {
                  "value": {}
                }
              }
            }
          }
        }
      }
    },
    "/v1/sequences": {
      "post": {
        "operationId": "startSequence",
        "summary": "Schedule a bounded impact sequence",
        "description": "1–16 steps; total delayMs sum <=5000. First delay may be 0, following delays >=50 ms. Retains ownership until completion/stop/replacement/output loss. No sequence updates or heartbeat extension.",
        "responses": {
          "200": {
            "description": "Command accepted or current state. Acceptance does not guarantee a physical sensation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SequenceResponse"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          },
          "400": {
            "description": "invalid_client, invalid_parameters, bad_request or incomplete_request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "invalid_host or browser_origin_denied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "busy, no_resonance, no_gesture or no_controlled_resonance, depending on endpoint/state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "411": {
            "description": "length_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "request_too_large or body_too_large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "json_required or encoding_not_supported.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "417": {
            "description": "expect_not_supported.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "431": {
            "description": "headers_too_large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "mode_inactive, unavailable, output_unavailable or gesture_unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientID"
          },
          {
            "$ref": "#/components/parameters/ClientName"
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Send JSON with exact Content-Length; no chunking, compression, Origin or Sec-Fetch-* headers.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Sequence"
              },
              "examples": {
                "doubleImpact": {
                  "value": {
                    "steps": [
                      {
                        "style": "crisp",
                        "intensity": 0.25,
                        "delayMs": 0
                      },
                      {
                        "style": "soft",
                        "intensity": 0.2,
                        "delayMs": 180
                      }
                    ]
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/heartbeat": {
      "post": {
        "operationId": "renewSession",
        "summary": "Renew an existing gesture or controlled session",
        "description": "Send every 500 ms only while the interaction is wanted, including controlled pause. Does not revive expired sessions. no_gesture also covers absent controlled sessions; retained for protocol compatibility. Send {} even though heartbeat does not inspect body fields.",
        "responses": {
          "200": {
            "description": "Command accepted or current state. Acceptance does not guarantee a physical sensation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          },
          "400": {
            "description": "invalid_client, invalid_parameters, bad_request or incomplete_request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "invalid_host or browser_origin_denied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "busy, no_resonance, no_gesture or no_controlled_resonance, depending on endpoint/state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "411": {
            "description": "length_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "request_too_large or body_too_large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "json_required or encoding_not_supported.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "417": {
            "description": "expect_not_supported.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "431": {
            "description": "headers_too_large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "mode_inactive, unavailable, output_unavailable or gesture_unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientID"
          },
          {
            "$ref": "#/components/parameters/ClientName"
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Send JSON with exact Content-Length; no chunking, compression, Origin or Sec-Fetch-* headers.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmptyCommand"
              },
              "examples": {
                "empty": {
                  "value": {}
                }
              }
            }
          }
        }
      }
    },
    "/v1/stop": {
      "post": {
        "operationId": "stopEffect",
        "summary": "Stop the current client’s effect",
        "description": "Idempotent when idle. Another owner returns busy. Serialize stop after any in-flight start; it cancels future emissions, not already-sent impulses. Send {} even though stop does not inspect body fields.",
        "responses": {
          "200": {
            "description": "Command accepted or current state. Acceptance does not guarantee a physical sensation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          },
          "400": {
            "description": "invalid_client, invalid_parameters, bad_request or incomplete_request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "invalid_host or browser_origin_denied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "busy, no_resonance, no_gesture or no_controlled_resonance, depending on endpoint/state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "411": {
            "description": "length_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "request_too_large or body_too_large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "json_required or encoding_not_supported.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "417": {
            "description": "expect_not_supported.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "431": {
            "description": "headers_too_large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "mode_inactive, unavailable, output_unavailable or gesture_unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientID"
          },
          {
            "$ref": "#/components/parameters/ClientName"
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Send JSON with exact Content-Length; no chunking, compression, Origin or Sec-Fetch-* headers.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmptyCommand"
              },
              "examples": {
                "empty": {
                  "value": {}
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "ClientID": {
        "name": "X-FMM-Client",
        "in": "header",
        "required": true,
        "description": "One UUID per running client instance, reused for all POSTs. Coordinates ownership; not authentication.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "ClientName": {
        "name": "X-FMM-Client-Name",
        "in": "header",
        "required": false,
        "description": "Optional ASCII display label. The host displays at most 64 characters. Not proof of application identity.",
        "schema": {
          "type": "string",
          "pattern": "^[\\t\\x20-\\x7E]*$"
        }
      }
    },
    "responses": {
      "Error": {
        "description": "JSON command or framing error. Connection limits, timeout and stopped listeners may instead close/refuse the connection without a response.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Normalized": {
        "type": "number",
        "minimum": 0,
        "maximum": 1
      },
      "Style": {
        "type": "string",
        "enum": [
          "crisp",
          "natural",
          "soft"
        ],
        "description": "Nominal impact profiles: crisp 2 ms, natural 3.8 ms, soft 6 ms. These are not measured physical frequencies."
      },
      "PulseRate": {
        "type": "number",
        "minimum": 1,
        "maximum": 60,
        "description": "Requested pulses per second, not physical actuator frequency."
      },
      "Sharpness": {
        "type": "number",
        "minimum": 0,
        "maximum": 1,
        "description": "Soft (0) to crisp (1), quantized to eight prepared gentle profiles."
      },
      "Transition": {
        "type": "number",
        "minimum": 0,
        "maximum": 1000,
        "default": 40,
        "description": "Milliseconds to approach subsequent target values. Start applies initial values directly; omitted updates retain the previous setting."
      },
      "Duration": {
        "type": "number",
        "minimum": 20,
        "maximum": 2000,
        "description": "Total milliseconds from the original timed-session start, not from the latest update."
      },
      "EmptyCommand": {
        "type": "object",
        "maxProperties": 0,
        "examples": [
          {}
        ]
      },
      "Impulse": {
        "type": "object",
        "properties": {
          "style": {
            "$ref": "#/components/schemas/Style"
          },
          "intensity": {
            "$ref": "#/components/schemas/Normalized"
          }
        },
        "required": [
          "style",
          "intensity"
        ]
      },
      "Success": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          }
        },
        "required": [
          "ok"
        ]
      },
      "Error": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        },
        "required": [
          "ok",
          "error"
        ]
      },
      "GestureResonance": {
        "type": "object",
        "properties": {
          "mode": {
            "type": "string",
            "const": "gesture"
          },
          "intensity": {
            "$ref": "#/components/schemas/Normalized"
          },
          "fineness": {
            "$ref": "#/components/schemas/Normalized"
          },
          "reactivity": {
            "$ref": "#/components/schemas/Normalized"
          }
        },
        "required": [
          "mode",
          "intensity",
          "fineness",
          "reactivity"
        ],
        "additionalProperties": false
      },
      "GestureUpdate": {
        "type": "object",
        "properties": {
          "intensity": {
            "$ref": "#/components/schemas/Normalized"
          },
          "fineness": {
            "$ref": "#/components/schemas/Normalized"
          },
          "reactivity": {
            "$ref": "#/components/schemas/Normalized"
          }
        },
        "additionalProperties": false,
        "minProperties": 1
      },
      "TimedResonance": {
        "type": "object",
        "properties": {
          "mode": {
            "type": "string",
            "const": "timed"
          },
          "intensity": {
            "$ref": "#/components/schemas/Normalized"
          },
          "fineness": {
            "$ref": "#/components/schemas/Normalized"
          },
          "durationMs": {
            "$ref": "#/components/schemas/Duration"
          }
        },
        "required": [
          "mode",
          "intensity",
          "fineness",
          "durationMs"
        ],
        "additionalProperties": false
      },
      "TimedUpdate": {
        "type": "object",
        "properties": {
          "intensity": {
            "$ref": "#/components/schemas/Normalized"
          },
          "fineness": {
            "$ref": "#/components/schemas/Normalized"
          },
          "durationMs": {
            "$ref": "#/components/schemas/Duration"
          }
        },
        "additionalProperties": false,
        "minProperties": 1
      },
      "ControlledResonance": {
        "type": "object",
        "properties": {
          "mode": {
            "type": "string",
            "const": "controlled"
          },
          "intensity": {
            "$ref": "#/components/schemas/Normalized"
          },
          "pulseRateHz": {
            "$ref": "#/components/schemas/PulseRate"
          },
          "sharpness": {
            "$ref": "#/components/schemas/Sharpness"
          },
          "transitionMs": {
            "$ref": "#/components/schemas/Transition"
          }
        },
        "required": [
          "mode",
          "intensity",
          "pulseRateHz",
          "sharpness"
        ],
        "additionalProperties": false
      },
      "ControlledUpdate": {
        "type": "object",
        "properties": {
          "intensity": {
            "$ref": "#/components/schemas/Normalized"
          },
          "pulseRateHz": {
            "$ref": "#/components/schemas/PulseRate"
          },
          "sharpness": {
            "$ref": "#/components/schemas/Sharpness"
          },
          "transitionMs": {
            "$ref": "#/components/schemas/Transition"
          }
        },
        "additionalProperties": false,
        "minProperties": 1
      },
      "ResonanceStart": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/GestureResonance"
          },
          {
            "$ref": "#/components/schemas/TimedResonance"
          },
          {
            "$ref": "#/components/schemas/ControlledResonance"
          }
        ],
        "discriminator": {
          "propertyName": "mode",
          "mapping": {
            "gesture": "#/components/schemas/GestureResonance",
            "timed": "#/components/schemas/TimedResonance",
            "controlled": "#/components/schemas/ControlledResonance"
          }
        }
      },
      "ResonanceUpdate": {
        "anyOf": [
          {
            "$ref": "#/components/schemas/GestureUpdate"
          },
          {
            "$ref": "#/components/schemas/TimedUpdate"
          },
          {
            "$ref": "#/components/schemas/ControlledUpdate"
          }
        ],
        "description": "At least one field, only fields applicable to the current session mode. This schema cannot infer active server state. Mode is immutable; null and unknown fields are rejected."
      },
      "FirstSequenceStep": {
        "type": "object",
        "properties": {
          "style": {
            "$ref": "#/components/schemas/Style"
          },
          "intensity": {
            "$ref": "#/components/schemas/Normalized"
          },
          "delayMs": {
            "type": "number",
            "minimum": 0,
            "maximum": 5000
          }
        },
        "required": [
          "style",
          "intensity",
          "delayMs"
        ]
      },
      "SequenceStep": {
        "type": "object",
        "properties": {
          "style": {
            "$ref": "#/components/schemas/Style"
          },
          "intensity": {
            "$ref": "#/components/schemas/Normalized"
          },
          "delayMs": {
            "type": "number",
            "minimum": 50,
            "maximum": 5000
          }
        },
        "required": [
          "style",
          "intensity",
          "delayMs"
        ]
      },
      "Sequence": {
        "type": "object",
        "properties": {
          "steps": {
            "type": "array",
            "minItems": 1,
            "maxItems": 16,
            "prefixItems": [
              {
                "$ref": "#/components/schemas/FirstSequenceStep"
              }
            ],
            "items": {
              "$ref": "#/components/schemas/SequenceStep"
            },
            "description": "Delay is before each impact. Sum of all delayMs must not exceed 5000; this cross-item sum is enforced by the server."
          }
        },
        "required": [
          "steps"
        ]
      },
      "ImpulseResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "const": true,
            "type": "boolean"
          },
          "emitted": {
            "type": "boolean",
            "description": "Whether an impulse was sent. False can reflect zero intensity, user limit, native rate suppression or output failure."
          }
        },
        "required": [
          "ok",
          "emitted"
        ]
      },
      "SequenceResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "const": true,
            "type": "boolean"
          },
          "activeEffect": {
            "const": "sequence",
            "type": "string"
          }
        },
        "required": [
          "ok",
          "activeEffect"
        ]
      },
      "ResonanceState": {
        "type": "object",
        "properties": {
          "ok": {
            "const": true,
            "type": "boolean"
          },
          "activeEffect": {
            "type": "string",
            "enum": [
              "none",
              "gesture",
              "timed",
              "controlled",
              "sequence"
            ]
          },
          "resonance": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/GestureResonance"
              },
              {
                "$ref": "#/components/schemas/TimedResonance"
              },
              {
                "$ref": "#/components/schemas/ControlledResonance"
              },
              {
                "type": "null"
              }
            ]
          },
          "remainingDurationMs": {
            "type": "number",
            "minimum": 0,
            "description": "Present for timed mode; zero when resonance is null. Omitted for gesture/controlled."
          },
          "paused": {
            "type": "boolean",
            "description": "Present for controlled mode."
          },
          "currentValues": {
            "type": "object",
            "properties": {
              "intensity": {
                "$ref": "#/components/schemas/Normalized"
              },
              "pulseRateHz": {
                "$ref": "#/components/schemas/PulseRate"
              },
              "sharpness": {
                "$ref": "#/components/schemas/Sharpness"
              }
            },
            "required": [
              "intensity",
              "pulseRateHz",
              "sharpness"
            ],
            "description": "Interpolated controlled values before the user intensity multiplier; present only in controlled mode."
          }
        },
        "required": [
          "ok",
          "activeEffect",
          "resonance"
        ]
      },
      "Status": {
        "type": "object",
        "properties": {
          "ok": {
            "const": true,
            "type": "boolean"
          },
          "activeEffect": {
            "type": "string",
            "enum": [
              "none",
              "gesture",
              "timed",
              "controlled",
              "sequence"
            ]
          },
          "resonance": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/GestureResonance"
              },
              {
                "$ref": "#/components/schemas/TimedResonance"
              },
              {
                "$ref": "#/components/schemas/ControlledResonance"
              },
              {
                "type": "null"
              }
            ]
          },
          "remainingDurationMs": {
            "type": "number",
            "minimum": 0,
            "description": "Present for timed mode; zero when resonance is null. Omitted for gesture/controlled."
          },
          "paused": {
            "type": "boolean",
            "description": "Present for controlled mode."
          },
          "currentValues": {
            "type": "object",
            "properties": {
              "intensity": {
                "$ref": "#/components/schemas/Normalized"
              },
              "pulseRateHz": {
                "$ref": "#/components/schemas/PulseRate"
              },
              "sharpness": {
                "$ref": "#/components/schemas/Sharpness"
              }
            },
            "required": [
              "intensity",
              "pulseRateHz",
              "sharpness"
            ],
            "description": "Interpolated controlled values before the user intensity multiplier; present only in controlled mode."
          },
          "version": {
            "const": 1,
            "type": "integer"
          },
          "authentication": {
            "const": "none",
            "type": "string"
          },
          "mode": {
            "type": "string",
            "enum": [
              "localAPI",
              "material"
            ]
          },
          "hapticEnabled": {
            "type": "boolean"
          },
          "customActuationAvailable": {
            "type": "boolean"
          },
          "gestureAvailable": {
            "type": "boolean"
          },
          "styles": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Style"
            }
          },
          "outputAvailable": {
            "type": "boolean"
          },
          "materialAudioEnabled": {
            "const": false,
            "type": "boolean"
          },
          "intensityLimit": {
            "$ref": "#/components/schemas/Normalized"
          },
          "gestureLeaseMs": {
            "type": "integer",
            "const": 2000
          },
          "controlledLeaseMs": {
            "type": "integer",
            "const": 2000
          },
          "maxTimedDurationMs": {
            "type": "integer",
            "const": 2000
          },
          "actuatorFrequencyControl": {
            "type": "boolean",
            "const": false
          }
        },
        "required": [
          "ok",
          "version",
          "authentication",
          "outputAvailable",
          "materialAudioEnabled",
          "activeEffect",
          "resonance",
          "intensityLimit",
          "gestureLeaseMs",
          "controlledLeaseMs",
          "maxTimedDurationMs",
          "actuatorFrequencyControl"
        ]
      },
      "Parameters": {
        "type": "object",
        "properties": {
          "ok": {
            "const": true,
            "type": "boolean"
          },
          "version": {
            "const": 1,
            "type": "integer"
          },
          "authentication": {
            "const": "none",
            "type": "string"
          },
          "impulses": {
            "type": "object"
          },
          "resonance": {
            "type": "object",
            "properties": {
              "gesture": {
                "type": "object"
              },
              "timed": {
                "type": "object"
              },
              "controlled": {
                "type": "object"
              }
            },
            "required": [
              "gesture",
              "timed",
              "controlled"
            ]
          },
          "resonanceUpdate": {
            "type": "object"
          },
          "resonancePause": {
            "type": "object"
          },
          "resonanceResume": {
            "type": "object"
          },
          "sequences": {
            "type": "object"
          },
          "gestureLeaseMs": {
            "type": "integer",
            "const": 2000
          },
          "controlledLeaseMs": {
            "type": "integer",
            "const": 2000
          },
          "recommendedHeartbeatMs": {
            "type": "integer",
            "const": 500
          },
          "maxEffectCommandsPerSecond": {
            "type": "integer",
            "const": 60
          },
          "recommendedUpdatesPerSecond": {
            "type": "integer",
            "const": 20
          },
          "materialAudioEnabled": {
            "type": "boolean",
            "const": false
          },
          "actuatorFrequencyControl": {
            "type": "boolean",
            "const": false
          }
        },
        "required": [
          "ok",
          "version",
          "authentication",
          "resonance",
          "recommendedHeartbeatMs",
          "materialAudioEnabled",
          "actuatorFrequencyControl"
        ],
        "description": "Capability metadata. Mode/field maps contain type, minimum, maximum, enum or defaults; clients should discover controls here instead of inferring them from an app version."
      }
    }
  },
  "x-protocol-version": 1,
  "x-transport": {
    "httpVersion": "1.1",
    "maximumHeaderBytes": 8192,
    "maximumBodyBytes": 16384,
    "maximumConnections": 8,
    "connectionDeadlineMs": 3000,
    "browserOriginsAllowed": false,
    "oneRequestPerConnection": true
  }
}
