{
  "endpoints": [
    {
      "methods": [
        "GET"
      ],
      "path": "/api/health",
      "title": "Check server availability",
      "category": "Server and authentication",
      "auth": "Public",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "ok": true,
        "name": "liveplay-server"
      },
      "notes": "",
      "errors": [
        [
          "500",
          "internal error"
        ]
      ],
      "sourceLine": 2028
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/whoami",
      "title": "Identify the requesting connection",
      "category": "Server and authentication",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "clientIp": "127.0.0.1",
        "isLocal": true
      },
      "notes": "isLocal describes the socket peer; it does not exempt a caller from authentication.",
      "errors": [],
      "sourceLine": 2040
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/clients",
      "title": "List connected WebSocket sessions",
      "category": "Server and authentication",
      "auth": "Administrator",
      "apiToken": "No",
      "fields": [],
      "request": null,
      "response": [
        {
          "id": 1,
          "remoteIp": "192.168.1.20",
          "user": "Operator",
          "userId": "user-id",
          "isAdmin": false,
          "kind": "user",
          "locale": "en",
          "localeIsOwn": false,
          "meterHz": 30,
          "meterHzIsOwn": false,
          "connectedSeconds": 12
        }
      ],
      "notes": "Includes sockets only, sorted by process-local monotonic id. The identity/role reflects the handshake; locale and meterHz are effective per-connection settings.",
      "errors": [
        [
          "500",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 2066,
      "responseSchema": "ClientSession[]"
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/auth/status",
      "title": "Read authentication posture",
      "category": "Server and authentication",
      "auth": "Public",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "authRequired": false,
        "userCount": 0,
        "setupRequired": true,
        "tokenTtlSeconds": 2592000
      },
      "notes": "setupRequired means there are no accounts, not merely that login is disabled.",
      "errors": [],
      "sourceLine": 2143
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/auth/login",
      "title": "Exchange a user password for a session token",
      "category": "Server and authentication",
      "auth": "Public",
      "apiToken": "Yes",
      "fields": [
        [
          "name",
          "string",
          "Yes",
          "-",
          "Account name."
        ],
        [
          "password",
          "string",
          "Yes",
          "-",
          "Account password."
        ]
      ],
      "request": {
        "name": "Operator",
        "password": "example-password"
      },
      "response": {
        "token": "lp1.example.signature",
        "expiresIn": 2592000,
        "user": {
          "id": "user-id",
          "name": "Operator",
          "role": "operator",
          "avatar": null
        }
      },
      "notes": "Login is refused with 409 whenever authentication is disabled, including when accounts still exist. Five failed attempts per address cause a 30-second delay; 429 includes Retry-After in seconds.",
      "errors": [
        [
          "400",
          "expected a JSON object"
        ],
        [
          "400",
          "name and password are required"
        ],
        [
          "409",
          "this server has no accounts — authentication is not in use"
        ],
        [
          "429",
          "too many failed attempts — wait and try again"
        ],
        [
          "401",
          "incorrect user name or password"
        ],
        [
          "500",
          "could not issue a token"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 2257
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/auth/me",
      "title": "Read the authenticated principal",
      "category": "Server and authentication",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "authRequired": true,
        "user": {
          "id": "user-id",
          "name": "Operator",
          "role": "operator",
          "kind": "user",
          "avatar": null
        }
      },
      "notes": "With authentication disabled: {\"authRequired\":false,\"user\":null}. kind is user or token. avatar is the account's picture as a data URL, or null (always null for an API token).",
      "errors": [],
      "sourceLine": 2311
    },
    {
      "methods": [
        "PUT"
      ],
      "path": "/api/auth/me/avatar",
      "title": "Set your own picture",
      "category": "Server and authentication",
      "auth": "Operator",
      "apiToken": "No",
      "fields": [
        [
          "avatar",
          "string",
          "Yes",
          "-",
          "PNG, JPEG or WebP data URL; decoded at most 128 KB; bytes must match the declared type. SVG is refused."
        ]
      ],
      "request": {
        "avatar": "data:image/webp;base64,UklGR…"
      },
      "response": {
        "id": "user-id",
        "avatar": "data:image/webp;base64,UklGR…"
      },
      "notes": "Always acts on the signed-in caller, so any person may change their own picture without being an administrator. Needs a signed-in person: 409 while authentication is off. Does not end the caller's sessions.",
      "errors": [
        [
          "400",
          "expected a JSON object"
        ],
        [
          "400",
          "\"avatar\" must be an image data URL — DELETE removes the picture"
        ],
        [
          "409",
          "no signed-in user — sign in to change your own picture"
        ],
        [
          "400",
          "the picture must be a PNG, JPEG or WebP image"
        ],
        [
          "413",
          "the picture is too large"
        ],
        [
          "404",
          "no such user"
        ],
        [
          "500",
          "the user store could not be written"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 2424
    },
    {
      "methods": [
        "DELETE"
      ],
      "path": "/api/auth/me/avatar",
      "title": "Remove your own picture",
      "category": "Server and authentication",
      "auth": "Operator",
      "apiToken": "No",
      "fields": [],
      "request": null,
      "response": {
        "id": "user-id",
        "avatar": null
      },
      "notes": "Same rules as PUT /api/auth/me/avatar.",
      "errors": [
        [
          "409",
          "no signed-in user — sign in to change your own picture"
        ],
        [
          "404",
          "no such user"
        ],
        [
          "500",
          "the user store could not be written"
        ]
      ],
      "sourceLine": 2437
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/auth/logout_all",
      "title": "Invalidate this user's session tokens",
      "category": "Server and authentication",
      "auth": "Operator",
      "apiToken": "No",
      "fields": [],
      "request": null,
      "response": {
        "ok": true
      },
      "notes": "Requires a signed-in person even when login is disabled (400 otherwise). Existing WebSocket connections are not disconnected.",
      "errors": [
        [
          "400",
          "not authenticated"
        ],
        [
          "500",
          "User/token store refusal; see endpoint notes for conditions."
        ]
      ],
      "sourceLine": 2334
    },
    {
      "methods": [
        "PATCH"
      ],
      "path": "/api/auth/required",
      "title": "Enable or disable login",
      "category": "Server and authentication",
      "auth": "Administrator",
      "apiToken": "No",
      "fields": [
        [
          "required",
          "boolean",
          "Yes",
          "-",
          "Desired posture."
        ],
        [
          "name",
          "string",
          "Conditional",
          "Authenticated name",
          "Administrator name; required explicitly when auth is off."
        ],
        [
          "password",
          "string",
          "Yes",
          "-",
          "Administrator password."
        ]
      ],
      "request": {
        "required": false,
        "password": "example-password"
      },
      "response": {
        "authRequired": false,
        "userCount": 1
      },
      "notes": "Accounts are retained. No accounts: 409. Rechecks the password and administrator role, with login throttling. Store I/O failure: 500. When authentication is off, the middleware permits the call but the password check still applies.",
      "errors": [
        [
          "400",
          "expected a JSON object"
        ],
        [
          "400",
          "\"required\" must be true or false"
        ],
        [
          "409",
          "this server has no accounts — authentication is already off"
        ],
        [
          "400",
          "an administrator's name and password are required to change this"
        ],
        [
          "429",
          "too many failed attempts — wait and try again"
        ],
        [
          "401",
          "incorrect user name or password"
        ],
        [
          "403",
          "only an administrator can change whether this server requires a login"
        ],
        [
          "409",
          "User/token store refusal; see endpoint notes for conditions."
        ],
        [
          "500",
          "User/token store refusal; see endpoint notes for conditions."
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 2188
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/users",
      "title": "List accounts",
      "category": "Users and API tokens",
      "auth": "Administrator",
      "apiToken": "No",
      "fields": [],
      "request": null,
      "response": [
        {
          "id": "user-id",
          "name": "Operator",
          "role": "operator",
          "createdAt": 1780000000,
          "avatar": null
        }
      ],
      "notes": "createdAt is Unix seconds. Password hashes and token epochs are not exposed. avatar is a PNG, JPEG or WebP data URL, or null.",
      "errors": [],
      "sourceLine": 2527
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/users",
      "title": "Create an account",
      "category": "Users and API tokens",
      "auth": "Administrator",
      "apiToken": "No",
      "fields": [
        [
          "name",
          "string",
          "Yes",
          "-",
          "Trimmed; 1..64 bytes; no control characters; unique ignoring ASCII case."
        ],
        [
          "password",
          "string",
          "Yes",
          "-",
          "At least 8 bytes."
        ],
        [
          "role",
          "string",
          "No",
          "operator",
          "admin or operator."
        ]
      ],
      "request": {
        "name": "Operator",
        "password": "example-password",
        "role": "operator"
      },
      "response": {
        "id": "user-id",
        "name": "Operator",
        "role": "operator"
      },
      "notes": "The first account is forced to admin and enables authentication. In the default open posture this bootstrap call needs no credential. Duplicate name: 409; validation: 400; hashing/storage failure: 500.",
      "errors": [
        [
          "400",
          "expected a JSON object"
        ],
        [
          "400",
          "role must be 'admin' or 'operator'"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ],
        [
          "400",
          "Invalid name or weak password."
        ],
        [
          "409",
          "Account name is already in use."
        ],
        [
          "500",
          "Password hashing or store write failed."
        ]
      ],
      "sourceLine": 2551
    },
    {
      "methods": [
        "PATCH"
      ],
      "path": "/api/users/<string>",
      "title": "Edit an account",
      "category": "Users and API tokens",
      "auth": "Administrator",
      "apiToken": "No",
      "fields": [
        [
          "name",
          "string",
          "No",
          "Unchanged",
          "Same name constraints as account creation."
        ],
        [
          "role",
          "string",
          "No",
          "Unchanged",
          "admin or operator."
        ],
        [
          "password",
          "string",
          "No",
          "Unchanged",
          "At least 8 bytes; invalidates that user's session tokens."
        ],
        [
          "avatar",
          "string|null",
          "No",
          "Unchanged",
          "PNG, JPEG or WebP data URL (decoded at most 128 KB; bytes must match the declared type); null clears. Checked before any other field is applied; does not end the user's sessions."
        ]
      ],
      "request": {
        "name": "Stage operator"
      },
      "response": {
        "id": "user-id",
        "name": "Stage operator",
        "role": "operator",
        "avatar": null
      },
      "notes": "Path: account id. Duplicate name or removing the last administrator role: 409. Validation: 400; storage: 500. Fields are applied sequentially, not transactionally.",
      "errors": [
        [
          "400",
          "expected a JSON object"
        ],
        [
          "404",
          "no such user"
        ],
        [
          "400",
          "name must be a string"
        ],
        [
          "400",
          "role must be 'admin' or 'operator'"
        ],
        [
          "400",
          "password must be a string"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ],
        [
          "413",
          "the picture is too large"
        ],
        [
          "400",
          "the picture must be a PNG, JPEG or WebP image"
        ],
        [
          "400",
          "Invalid name/password or hashing failure."
        ],
        [
          "409",
          "Name conflict or attempt to demote the last administrator."
        ],
        [
          "500",
          "User store write failed."
        ]
      ],
      "sourceLine": 2592
    },
    {
      "methods": [
        "DELETE"
      ],
      "path": "/api/users/<string>",
      "title": "Delete an account",
      "category": "Users and API tokens",
      "auth": "Administrator",
      "apiToken": "No",
      "fields": [],
      "request": null,
      "response": {
        "ok": true
      },
      "notes": "Path: account id. Last administrator cannot be deleted (409). Storage failure: 500. Removes the user preference profile; invalidates credentials for subsequent requests.",
      "errors": [
        [
          "404",
          "no such user"
        ],
        [
          "409",
          "User/token store refusal; see endpoint notes for conditions."
        ],
        [
          "500",
          "User/token store refusal; see endpoint notes for conditions."
        ]
      ],
      "sourceLine": 2636
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/users/export",
      "title": "Export accounts",
      "category": "Users and API tokens",
      "auth": "Administrator",
      "apiToken": "No",
      "fields": [],
      "request": null,
      "response": {
        "format": "liveplay-users",
        "version": 1,
        "exportedAt": 1780000000,
        "users": [
          {
            "id": "user-id",
            "name": "Operator",
            "role": "operator",
            "hash": "$argon2id$v=19$m=65536,t=2,p=1$…",
            "tokenEpoch": 1,
            "createdAt": 1780000000,
            "avatar": null
          }
        ],
        "apiTokens": [
          {
            "id": "token-id",
            "name": "Companion",
            "hash": "base64url-blake2b",
            "createdBy": "user-id",
            "createdAt": 1780000000,
            "lastUsedAt": 0
          }
        ]
      },
      "notes": "Contains password and API token hashes: store it securely. Never includes the session-signing secret (sessions do not transfer between machines) or the login on/off choice (that stays with each machine). Needs a signed-in administrator even when authentication is off (409 otherwise). Sent with Cache-Control: no-store.",
      "errors": [
        [
          "409",
          "turn the login on and sign in as an administrator to export accounts — the file contains password hashes"
        ]
      ],
      "sourceLine": 2810
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/users/import",
      "title": "Import accounts",
      "category": "Users and API tokens",
      "auth": "Administrator",
      "apiToken": "No",
      "fields": [
        [
          "mode",
          "string",
          "Yes",
          "-",
          "merge (add accounts and tokens whose names are new here; skip the rest) or replace (the file becomes the account and token list)."
        ],
        [
          "data",
          "object",
          "Yes",
          "-",
          "A file produced by GET /api/users/export."
        ]
      ],
      "request": {
        "mode": "merge",
        "data": {
          "format": "liveplay-users",
          "version": 1,
          "users": [],
          "apiTokens": []
        }
      },
      "response": {
        "mode": "merge",
        "usersAdded": [
          "Operator"
        ],
        "usersSkipped": [],
        "usersRemoved": 0,
        "tokensAdded": [],
        "tokensSkipped": [],
        "idsRegenerated": 0,
        "authRequired": true,
        "userCount": 2,
        "sessionValid": true
      },
      "notes": "The whole file is validated before anything changes (format and version, Argon2id hashes, names, roles, unique ids, pictures, token hashes). Merge gives an account a new id if its id is taken here; a token whose id is taken is skipped, because the id is part of the token string. Replace refuses a file with no accounts. Either mode refuses a result with accounts and no administrator (409), and adding API tokens while the login would stay off (409). Open while authentication is off, like the other /api/users routes, so a fresh server can take accounts; the login is on as soon as they land. sessionValid says whether the caller's own session survived (a replace can remove or change their account). Removed accounts' preferences are deleted.",
      "errors": [
        [
          "400",
          "expected a JSON object"
        ],
        [
          "400",
          "mode must be 'merge' or 'replace'"
        ],
        [
          "400",
          "\"data\" is required"
        ],
        [
          "400",
          "Validation failure naming the file problem or record, e.g. account 'Operator' does not have a valid password hash."
        ],
        [
          "409",
          "the result would have no administrator — nobody could manage the accounts afterwards"
        ],
        [
          "409",
          "turn the login on before issuing an API token — while it is off, anyone on the network could issue one and keep it"
        ],
        [
          "500",
          "the user store could not be written"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 2837
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/tokens",
      "title": "List automation credentials",
      "category": "Users and API tokens",
      "auth": "Administrator",
      "apiToken": "No",
      "fields": [],
      "request": null,
      "response": [
        {
          "id": "token-id",
          "name": "Companion",
          "createdBy": "admin-id",
          "createdAt": 1780000000,
          "lastUsedAt": null
        }
      ],
      "notes": "Timestamps are Unix seconds. createdBy and lastUsedAt can be null. No secret is returned.",
      "errors": [],
      "sourceLine": 2681
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/tokens",
      "title": "Issue a non-expiring automation credential",
      "category": "Users and API tokens",
      "auth": "Administrator",
      "apiToken": "No",
      "fields": [
        [
          "name",
          "string",
          "Yes",
          "-",
          "Nonempty trimmed name; same name validation as accounts, unique among API tokens."
        ],
        [
          "password",
          "string",
          "Yes",
          "-",
          "Current administrator password."
        ]
      ],
      "request": {
        "name": "Companion",
        "password": "example-password"
      },
      "response": {
        "id": "token-id",
        "name": "Companion",
        "createdAt": 1780000000,
        "token": "lpk1_example"
      },
      "notes": "Requires authentication to be enabled and a signed-in administrator. The secret is returned once. Duplicate name/auth off: 409; validation: 400; storage: 500. Wrong password is throttled as for login.",
      "errors": [
        [
          "400",
          "expected a JSON object"
        ],
        [
          "400",
          "a token needs a name"
        ],
        [
          "409",
          "User/token store refusal; see endpoint notes for conditions."
        ],
        [
          "400",
          "your password is required to issue a token"
        ],
        [
          "429",
          "too many failed attempts — wait and try again"
        ],
        [
          "401",
          "incorrect password"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ],
        [
          "400",
          "Invalid token name."
        ],
        [
          "409",
          "Name conflict or authentication disabled."
        ],
        [
          "500",
          "Token store write failed."
        ]
      ],
      "sourceLine": 2701
    },
    {
      "methods": [
        "PATCH"
      ],
      "path": "/api/tokens/<string>",
      "title": "Rename an automation credential",
      "category": "Users and API tokens",
      "auth": "Administrator",
      "apiToken": "No",
      "fields": [
        [
          "name",
          "string",
          "Yes",
          "-",
          "Same validation as creation."
        ]
      ],
      "request": {
        "name": "FOH Companion"
      },
      "response": {
        "id": "token-id",
        "name": "FOH Companion"
      },
      "notes": "Path: token record id, not the secret. Name conflict: 409; invalid name/store failure: 400.",
      "errors": [
        [
          "400",
          "expected a JSON object"
        ],
        [
          "400",
          "name must be a string"
        ],
        [
          "404",
          "User/token store refusal; see endpoint notes for conditions."
        ],
        [
          "409",
          "User/token store refusal; see endpoint notes for conditions."
        ],
        [
          "400",
          "User/token store refusal; see endpoint notes for conditions."
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 2758
    },
    {
      "methods": [
        "DELETE"
      ],
      "path": "/api/tokens/<string>",
      "title": "Revoke an automation credential",
      "category": "Users and API tokens",
      "auth": "Administrator",
      "apiToken": "No",
      "fields": [],
      "request": null,
      "response": {
        "ok": true
      },
      "notes": "Path: token record id. While authentication is required, subsequent HTTP requests and new WebSocket handshakes fail; established sockets stop accepting commands and close with code 1008. Storage failure: 500.",
      "errors": [
        [
          "404",
          "User/token store refusal; see endpoint notes for conditions."
        ],
        [
          "500",
          "User/token store refusal; see endpoint notes for conditions."
        ]
      ],
      "sourceLine": 2775
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/prefs",
      "title": "Read the signed-in user's sparse profile",
      "category": "Configuration and preferences",
      "auth": "Operator",
      "apiToken": "No",
      "fields": [],
      "request": null,
      "response": {
        "theme": {
          "mode": "dark"
        },
        "meterMode": "LUFS"
      },
      "notes": "409 without an authenticated person. A missing profile can be seeded from compatible preference fields in the open project.",
      "errors": [
        [
          "409",
          "no signed-in user — preferences are local to this surface while authentication is off"
        ]
      ],
      "sourceLine": 2356,
      "responseSchema": "UserPrefs"
    },
    {
      "methods": [
        "PATCH"
      ],
      "path": "/api/prefs",
      "title": "Update user preferences",
      "category": "Configuration and preferences",
      "auth": "Operator",
      "apiToken": "No",
      "fields": [
        [
          "(body)",
          "UserPrefs object",
          "Yes",
          "-",
          "See shared UserPrefs schema; all keys optional."
        ]
      ],
      "request": {
        "meterMode": "dBFS"
      },
      "response": {
        "meterMode": "dBFS"
      },
      "notes": "Top-level null removes an override. Theme keys merge; playbackKeys replaces its map. Invalid/unknown values are dropped. Broadcasts prefs_changed only to this user's sockets. Write failure: 500.",
      "errors": [
        [
          "409",
          "no signed-in user — preferences are local to this surface while authentication is off"
        ],
        [
          "400",
          "expected an object"
        ],
        [
          "500",
          "Operation failed; diagnostic message in error. See behavioral notes."
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 2378,
      "responseSchema": "UserPrefs"
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/server/config",
      "title": "Read boot configuration and field provenance",
      "category": "Configuration and preferences",
      "auth": "Administrator",
      "apiToken": "No",
      "fields": [],
      "request": null,
      "response": {
        "path": "C:/LivePlay/liveplay.json",
        "schemaVersion": 1,
        "locked": false,
        "fields": [
          {
            "key": "port",
            "flag": "--port",
            "help": "Port to listen on.",
            "policy": false,
            "source": "default",
            "stored": null,
            "value": 4480,
            "appliesAt": "restart",
            "overridden": false,
            "type": "int",
            "min": 1,
            "max": 65535
          }
        ]
      },
      "notes": "Example contains one of the 13 fields. source: default/file/env/cli. value is the effective boot value; stored is the current file override or null. All registered fields apply on restart.",
      "errors": [],
      "sourceLine": 2428,
      "responseSchema": "ServerConfigRead"
    },
    {
      "methods": [
        "PATCH"
      ],
      "path": "/api/server/config",
      "title": "Store boot configuration overrides",
      "category": "Configuration and preferences",
      "auth": "Administrator",
      "apiToken": "No",
      "fields": [
        [
          "(body)",
          "ServerConfig object",
          "Yes",
          "-",
          "See shared ServerConfig schema; null clears a key."
        ]
      ],
      "request": {
        "meterHz": 30
      },
      "response": {
        "stored": {
          "schema_version": 1,
          "meterHz": 30
        },
        "dropped": [],
        "restartRequired": [
          "meterHz"
        ],
        "overriddenAtLaunch": []
      },
      "notes": "Does not restart/reconfigure the running engine. Unknown/invalid keys are reported in dropped. lockServerConfig cannot be changed here. Locked server: 403; write failure: 500.",
      "errors": [
        [
          "400",
          "expected an object"
        ],
        [
          "403",
          "Server configuration is locked."
        ],
        [
          "500",
          "Configuration file could not be written."
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 2481,
      "responseSchema": "ServerConfigPatch"
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/ui/showmode",
      "title": "Read shared show-mode signal",
      "category": "Configuration and preferences",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "enabled": false
      },
      "notes": "",
      "errors": [],
      "sourceLine": 3109
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/ui/showmode",
      "title": "Set or toggle shared show mode",
      "category": "Configuration and preferences",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "enabled",
          "boolean",
          "No",
          "Toggle",
          "If missing or not boolean, toggles."
        ]
      ],
      "request": {
        "enabled": true
      },
      "response": {
        "ok": true,
        "enabled": true
      },
      "notes": "Empty body is accepted. Broadcasts show_mode_changed; not persisted in the project.",
      "errors": [
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 3113
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/ui/locale",
      "title": "Read installation fallback locale",
      "category": "Configuration and preferences",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "locale": "en"
      },
      "notes": "",
      "errors": [],
      "sourceLine": 3131
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/ui/locale",
      "title": "Set installation fallback locale",
      "category": "Configuration and preferences",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "locale",
          "string",
          "Yes",
          "-",
          "Locale code; empty string is ignored by the state setter."
        ]
      ],
      "request": {
        "locale": "en"
      },
      "response": {
        "ok": true,
        "locale": "en"
      },
      "notes": "Broadcasts locale_changed only to sockets without their own locale. This is different from the per-socket set_locale command.",
      "errors": [
        [
          "400",
          "expected \"locale\""
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 3134
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/state/summary",
      "title": "Read compact control-surface state",
      "category": "Transport and selection",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "project": {
          "name": "Show",
          "itemCount": 1,
          "hasOpenProject": true,
          "audioLoading": false
        },
        "playing": [],
        "next": null,
        "selection": null,
        "ui": {
          "showMode": false,
          "locale": "en"
        },
        "master": {
          "gainDb": 0,
          "limiterEnabled": true
        },
        "cart": [],
        "buses": [],
        "server": {
          "version": "2.5.0",
          "meterBroadcastHz": 30,
          "masterChannels": 32,
          "previewMasterL": 30,
          "previewMasterR": 31,
          "maxUploadBytes": 268435456
        },
        "preview": {
          "active": false,
          "itemUuid": ""
        }
      },
      "notes": "See schema for nonempty playing/next/selection/cart/buses entries. Project itemCount counts only top-level playlist items.",
      "errors": [
        [
          "500",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ],
        [
          "500",
          "internal error"
        ]
      ],
      "sourceLine": 2974,
      "responseSchema": "StateSummary"
    },
    {
      "methods": [
        "POST",
        "GET"
      ],
      "path": "/api/transport/go",
      "title": "Trigger the armed or derived next item",
      "category": "Transport and selection",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "ok": true,
        "uuid": "item-uuid"
      },
      "notes": "POST and GET both act. Uses the armed override, then derives the next item from active playback. Does not implicitly start the first cue of an idle playlist.",
      "errors": [
        [
          "404",
          "nothing armed or playing to GO to"
        ]
      ],
      "sourceLine": 3005
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/transport/stop_all",
      "title": "Stop all show cues",
      "category": "Transport and selection",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "fade_ms",
          "integer (ms)",
          "No",
          "Project stopAllFadeMs, else 1000",
          "Explicit common stop fade."
        ]
      ],
      "request": {},
      "response": {
        "ok": true
      },
      "notes": "Empty body is accepted. Stops Preview only if settings.stopAllStopsPreview is true (default true). Does not clear PFL. Negative fade values are clamped by project state.",
      "errors": [
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 2956
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/selection",
      "title": "Read shared playlist selection",
      "category": "Transport and selection",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "itemUuid": "item-uuid"
      },
      "notes": "Empty string means no selection.",
      "errors": [],
      "sourceLine": 3026
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/selection",
      "title": "Select an item or step through the playlist",
      "category": "Transport and selection",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "itemUuid",
          "string",
          "One of",
          "-",
          "Select by UUID; empty clears."
        ],
        [
          "delta",
          "integer",
          "One of",
          "-",
          "Nonzero step; takes precedence over itemUuid."
        ]
      ],
      "request": {
        "delta": 1
      },
      "response": {
        "ok": true,
        "itemUuid": "item-uuid"
      },
      "notes": "Selection is shared session state. Without a selection, stepping uses active playback as an anchor. UUID is not validated by the setter.",
      "errors": [
        [
          "400",
          "delta must be non-zero"
        ],
        [
          "404",
          "playlist is empty"
        ],
        [
          "400",
          "expected \"itemUuid\" or \"delta\""
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 3034
    },
    {
      "methods": [
        "POST",
        "GET"
      ],
      "path": "/api/transport/arm_selected",
      "title": "Arm the selection for GO",
      "category": "Transport and selection",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "ok": true,
        "itemUuid": "item-uuid"
      },
      "notes": "POST and GET both act. 404 if no selection.",
      "errors": [
        [
          "404",
          "nothing is selected"
        ]
      ],
      "sourceLine": 3056
    },
    {
      "methods": [
        "POST",
        "GET"
      ],
      "path": "/api/transport/play_selected",
      "title": "Trigger the selected item",
      "category": "Transport and selection",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "ok": true,
        "itemUuid": "item-uuid"
      },
      "notes": "POST and GET both act; handles group triggers. 404 without a selection or playable target.",
      "errors": [
        [
          "404",
          "nothing is selected"
        ],
        [
          "404",
          "item not loaded into engine"
        ]
      ],
      "sourceLine": 3067
    },
    {
      "methods": [
        "POST",
        "GET"
      ],
      "path": "/api/transport/pause_toggle",
      "title": "Pause active cues or resume paused cues",
      "category": "Transport and selection",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "ok": true,
        "resumed": false
      },
      "notes": "POST and GET both act. If any project cues are paused, resumes those; otherwise pauses sounding project cues. Does not include Preview or engine-only cues in the summary.",
      "errors": [
        [
          "404",
          "nothing is on air"
        ]
      ],
      "sourceLine": 3082
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/transport/play_index",
      "title": "Trigger by playlist child indices",
      "category": "Transport and selection",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "index",
          "integer[]",
          "Yes",
          "-",
          "Nonempty, zero-based, nonnegative child indices."
        ]
      ],
      "request": {
        "index": [
          0,
          1
        ]
      },
      "response": {
        "ok": true,
        "uuid": "item-uuid",
        "index": [
          0,
          1
        ]
      },
      "notes": "Indices address the tree, independent of the UI index display offset.",
      "errors": [
        [
          "400",
          "index must contain non-negative integers"
        ],
        [
          "400",
          "index must be a non-empty array of child indices"
        ],
        [
          "404",
          "no item at that index"
        ],
        [
          "404",
          "item not loaded into engine"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 3164
    },
    {
      "methods": [
        "POST",
        "GET"
      ],
      "path": "/api/transport/cart/<int>/play",
      "title": "Trigger a cart slot",
      "category": "Transport and selection",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "ok": true,
        "slot": 0,
        "uuid": "item-uuid"
      },
      "notes": "Path: zero-based slot. POST and GET both act. Empty slot/unplayable target: 404.",
      "errors": [
        [
          "404",
          "cart slot is empty"
        ],
        [
          "404",
          "item not loaded into engine"
        ]
      ],
      "sourceLine": 3188
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/project",
      "title": "Read the complete project document",
      "category": "Projects",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "name": "Show",
        "version": "2.5.0",
        "items": [],
        "cartItems": [],
        "cartOnlyItems": [],
        "settings": {
          "outputTarget": "ebu-r128"
        },
        "buses": [],
        "busSchema": 2,
        "server": {
          "projectFilePath": "C:/Shows/Show/show.liveplay",
          "mediaRoot": "C:/Shows/Show/media",
          "audioLoading": false,
          "audioLoaded": 0,
          "audioTotal": 0
        }
      },
      "notes": "Document-shaped response, not a fixed whitelist. Example is abbreviated; derived outputTargetLevels and live cueId annotations are included by the server.",
      "errors": [],
      "sourceLine": 4155,
      "responseSchema": "ProjectDocument"
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/project/header",
      "title": "Read project metadata without the playlist",
      "category": "Projects",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "name": "Show",
        "version": "2.5.0",
        "folderPath": "C:/Shows/Show",
        "createdAt": "2026-09-27T12:00:00Z",
        "lastModified": "2026-09-27T12:00:00Z",
        "theme": {},
        "settings": {},
        "cartItems": [],
        "cartSlotKeys": {},
        "playbackKeys": {},
        "cartOnlyItems": [],
        "itemCount": 0,
        "busSchema": 2,
        "hasOpenProject": true,
        "server": {
          "projectFilePath": "C:/Shows/Show/show.liveplay",
          "mediaRoot": "C:/Shows/Show/media",
          "audioLoading": false,
          "audioLoaded": 0,
          "audioTotal": 0
        }
      },
      "notes": "No items or buses array. settings includes derived outputTargetLevels (omitted in this example for space). Fetch buses separately.",
      "errors": [],
      "sourceLine": 4162,
      "responseSchema": "ProjectHeader"
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/project/items",
      "title": "Read a page of top-level playlist items",
      "category": "Projects",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "?offset",
          "integer",
          "No",
          "0",
          "Clamped to 0..total; invalid text uses default."
        ],
        [
          "?limit",
          "integer",
          "No",
          "100",
          "Clamped to 1..1000; invalid text uses default."
        ]
      ],
      "request": null,
      "response": {
        "offset": 0,
        "limit": 100,
        "total": 0,
        "items": []
      },
      "notes": "Nested group children travel with the top-level item. Audio items have cueId when loaded. Request example uses ?offset=0&limit=100.",
      "errors": [],
      "sourceLine": 4177,
      "responseSchema": "ItemPage"
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/project/progress",
      "title": "Poll asynchronous audio loading",
      "category": "Projects",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "loading": false,
        "loaded": 1,
        "total": 1
      },
      "notes": "loading false does not guarantee every media file decoded; inspect item cueId and playback errors.",
      "errors": [],
      "sourceLine": 4201
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/project/load",
      "title": "Load a file or an inline project",
      "category": "Projects",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "path",
          "string",
          "One of",
          "-",
          "Server-side .liveplay path; takes precedence."
        ],
        [
          "document",
          "ProjectDocument",
          "One of",
          "-",
          "Inline document when path is absent."
        ]
      ],
      "request": {
        "path": "C:/Shows/Show/show.liveplay"
      },
      "response": {
        "name": "Show",
        "itemCount": 1,
        "needsRepair": false
      },
      "notes": "Returns ProjectHeader plus needsRepair and optional repairIssues/migration; example is abbreviated. Audio loading can continue after response. Broadcasts project_changed. Loading replaces the active project.",
      "errors": [
        [
          "400",
          "load failed"
        ],
        [
          "400",
          "expected 'path' or 'document'"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ],
        [
          "403",
          "Path is outside configured filesystem roots."
        ]
      ],
      "sourceLine": 4210,
      "responseSchema": "ProjectLoad"
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/project/close",
      "title": "Close the current project",
      "category": "Projects",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "closed": true
      },
      "notes": "Resets project/engine state and broadcasts project_changed.",
      "errors": [
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 4275
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/project/save",
      "title": "Save the current document",
      "category": "Projects",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "path",
          "string",
          "No",
          "Current project file",
          "Server-side destination; establishes folder/media root."
        ],
        [
          "document",
          "ProjectDocument",
          "No",
          "Current in-memory document",
          "Optional replacement before saving."
        ]
      ],
      "request": {},
      "response": {
        "ok": true,
        "path": "C:/Shows/Show/show.liveplay"
      },
      "notes": "JSON body is required (use {}). No configured path: 400. Save does not copy media for Save As. A rejected embedded document is logged and the existing document is still saved. May include migration summary; ordinary save does not emit project_changed.",
      "errors": [
        [
          "400",
          "no project file path set"
        ],
        [
          "500",
          "save failed"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ],
        [
          "403",
          "Path is outside configured filesystem roots."
        ]
      ],
      "sourceLine": 4554
    },
    {
      "methods": [
        "PUT"
      ],
      "path": "/api/project/document",
      "title": "Replace the in-memory project document",
      "category": "Projects",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "(body)",
          "ProjectDocument",
          "Yes",
          "-",
          "Full document, not a document wrapper."
        ]
      ],
      "request": {
        "name": "Show",
        "version": "2.5.0",
        "items": [],
        "cartItems": [],
        "cartOnlyItems": [],
        "settings": {}
      },
      "response": {
        "name": "Show",
        "itemCount": 0,
        "busSchema": 2
      },
      "notes": "Returns header (example abbreviated), with optional migration. Broadcasts project_changed. Does not write the .liveplay file; use save.",
      "errors": [
        [
          "400",
          "document not accepted"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 4623,
      "responseSchema": "ProjectHeader"
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/project/repair",
      "title": "Repair duplicate entries and save if a path exists",
      "category": "Projects",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "repaired": false,
        "issues": [],
        "saved": true
      },
      "notes": "issues is a string array. With no project path, saved is false. Repair and write failures have distinct outcomes.",
      "errors": [
        [
          "500",
          "repair succeeded but save failed"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 4519
    },
    {
      "methods": [
        "PATCH"
      ],
      "path": "/api/project/settings",
      "title": "Patch registered project settings",
      "category": "Projects",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "(body)",
          "ProjectSettings",
          "Yes",
          "-",
          "Partial settings object; see shared schema."
        ]
      ],
      "request": {
        "stopAllFadeMs": 250
      },
      "response": {
        "stopAllFadeMs": 250,
        "outputTargetLevels": {
          "blueBelow": -28,
          "greenMin": -28,
          "greenMax": -20,
          "yellowMin": -20,
          "yellowMax": -1,
          "redAbove": -1,
          "limiterCeilingDb": -1,
          "autoVolumeTargetDb": -23,
          "meterUnit": "LUFS",
          "waveformColor": "#00e676"
        }
      },
      "notes": "Returns the entire effective settings object. Unknown/invalid/derived/profile keys are dropped and logged, not returned as an error. Broadcasts settings_patched. Persist with project/save.",
      "errors": [
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 5032,
      "responseSchema": "ProjectSettings"
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/project/items",
      "title": "Add a playlist or cart-only item",
      "category": "Playlist items and carts",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "item",
          "ProjectItem object",
          "Yes",
          "-",
          "Include nonempty unique uuid and type; see shared schema."
        ],
        [
          "parentUuid",
          "string",
          "No",
          "Empty (root)",
          "Parent group UUID."
        ],
        [
          "cartOnly",
          "boolean",
          "No",
          "false",
          "Add to cartOnlyItems instead of playlist."
        ]
      ],
      "request": {
        "item": {
          "uuid": "opening",
          "type": "audio",
          "displayName": "Opening",
          "mediaPath": "media/opening.wav",
          "volume": 1
        }
      },
      "response": {
        "ok": true,
        "uuid": "opening",
        "cueId": "cue-id"
      },
      "notes": "Returned cueId can be empty (groups or failed load). Missing uuid/duplicate uuid/unloadable media is not reliably reported as an HTTP error. Re-read the document. Broadcast item_added echoes the submitted item, not a canonical inserted object.",
      "errors": [
        [
          "400",
          "missing 'item' object"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 4662
    },
    {
      "methods": [
        "PATCH"
      ],
      "path": "/api/project/items/<string>",
      "title": "Patch an item",
      "category": "Playlist items and carts",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "(body)",
          "ProjectItem patch",
          "Yes",
          "-",
          "Fields merge into the item; use shared schema."
        ]
      ],
      "request": {
        "volume": 0.5
      },
      "response": {
        "ok": true,
        "uuid": "item-uuid"
      },
      "notes": "Path: project UUID. Broadcasts item_updated with the submitted patch. No strict field whitelist or schema validation; malformed types can fail in state/engine code. Save separately.",
      "errors": [
        [
          "404",
          "item not found"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 4701
    },
    {
      "methods": [
        "DELETE"
      ],
      "path": "/api/project/items/<string>",
      "title": "Remove an item",
      "category": "Playlist items and carts",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "ok": true,
        "uuid": "item-uuid"
      },
      "notes": "Path: project UUID. Broadcasts item_removed.",
      "errors": [
        [
          "404",
          "item not found"
        ]
      ],
      "sourceLine": 4724
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/project/items/reorder",
      "title": "Reorder children",
      "category": "Playlist items and carts",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "parentUuid",
          "string",
          "No",
          "Empty (root)",
          "Group whose children are reordered."
        ],
        [
          "uuids",
          "string[]",
          "No",
          "[]",
          "Desired order; non-string elements ignored."
        ]
      ],
      "request": {
        "uuids": [
          "opening",
          "sting"
        ]
      },
      "response": {
        "ok": true
      },
      "notes": "Broadcasts items_reordered; missing/unknown IDs are not rejected at the route boundary. Save separately.",
      "errors": [
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 4740
    },
    {
      "methods": [
        "POST",
        "GET"
      ],
      "path": "/api/project/items/<string>/play",
      "title": "Trigger an item or group",
      "category": "Playlist items and carts",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "ok": true
      },
      "notes": "Path: project UUID. POST and GET both act. Applies project trigger behavior; an already active item can follow trigger semantics rather than an unconditional restart.",
      "errors": [
        [
          "404",
          "item not loaded into engine"
        ]
      ],
      "sourceLine": 4772
    },
    {
      "methods": [
        "POST",
        "GET"
      ],
      "path": "/api/project/items/by-index/<path>",
      "title": "Trigger an item by index path",
      "category": "Playlist items and carts",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "ok": true,
        "uuid": "item-uuid",
        "index": [
          0,
          1
        ]
      },
      "notes": "Path: nonempty zero-based nonnegative integer sequence, separated by slash or comma; e.g. /api/project/items/by-index/0/1. POST and GET both act. Empty segments are skipped. UI display numbering does not change API indices.",
      "errors": [
        [
          "400",
          "invalid index path"
        ],
        [
          "404",
          "no item at that index"
        ],
        [
          "404",
          "item not loaded into engine"
        ]
      ],
      "sourceLine": 4800
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/project/items/<string>/stop",
      "title": "Stop an item or group",
      "category": "Playlist items and carts",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "fade_ms",
          "number (ms)",
          "No",
          "Item manual-stop fade",
          "Truncated to integer and clamped to at least 0; 0 cuts."
        ]
      ],
      "request": {
        "fade_ms": 250
      },
      "response": {
        "ok": true
      },
      "notes": "Path: project UUID. Empty body allowed. Missing/non-numeric fade_ms uses cue settings: manualStopFade, else max(stopFade, fadeOutDuration), in seconds.",
      "errors": [
        [
          "400",
          "body must be JSON"
        ],
        [
          "404",
          "item not loaded into engine"
        ]
      ],
      "sourceLine": 4845
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/project/items/<string>/pause",
      "title": "Pause a loaded audio item",
      "category": "Playlist items and carts",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "ok": true
      },
      "notes": "Path: project UUID. Requires a loaded engine cue.",
      "errors": [
        [
          "404",
          "item not loaded into engine"
        ]
      ],
      "sourceLine": 4867
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/project/items/<string>/resume",
      "title": "Resume a loaded audio item",
      "category": "Playlist items and carts",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "ok": true
      },
      "notes": "Path: project UUID. Requires a loaded engine cue.",
      "errors": [
        [
          "404",
          "item not loaded into engine"
        ]
      ],
      "sourceLine": 4880
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/project/items/<string>/seek",
      "title": "Seek a project cue",
      "category": "Playlist items and carts",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "seconds",
          "number",
          "No",
          "0",
          "Absolute file position in seconds."
        ]
      ],
      "request": {
        "seconds": 12.5
      },
      "response": {
        "ok": true
      },
      "notes": "Path: project UUID. Requires item-to-cue mapping; if mapping exists but the engine cue is absent, the route still returns success.",
      "errors": [
        [
          "404",
          "item not loaded into engine"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 4893
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/project/cart",
      "title": "Bind a cart slot",
      "category": "Playlist items and carts",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "slot",
          "integer",
          "Yes",
          "-",
          "Use 0..63; route only checks nonnegative."
        ],
        [
          "itemUuid",
          "string",
          "Yes",
          "-",
          "Nonempty project UUID."
        ]
      ],
      "request": {
        "slot": 0,
        "itemUuid": "opening"
      },
      "response": {
        "ok": true,
        "slot": 0,
        "itemUuid": "opening"
      },
      "notes": "State rejects slot >=64 but the route still returns 200 and broadcasts cart_slot_set. UUID existence is not checked. Cart display size is a client concern.",
      "errors": [
        [
          "400",
          "slot and itemUuid required"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 4917
    },
    {
      "methods": [
        "DELETE"
      ],
      "path": "/api/project/cart/<int>",
      "title": "Clear a cart binding",
      "category": "Playlist items and carts",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "ok": true,
        "slot": 0
      },
      "notes": "Path: zero-based slot; 404 when no binding. Broadcasts cart_slot_cleared.",
      "errors": [
        [
          "404",
          "no binding at slot"
        ]
      ],
      "sourceLine": 4942
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/devices",
      "title": "Enumerate audio output devices",
      "category": "Audio devices and engine cues",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": [
        {
          "id": "device-id",
          "display_name": "Speakers",
          "channel_count": 2,
          "sample_rate": 48000,
          "is_default": true
        }
      ],
      "notes": "Also refreshes the project's device-name cache.",
      "errors": [
        [
          "500",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ],
        [
          "500",
          "unknown error enumerating devices"
        ]
      ],
      "sourceLine": 2788,
      "responseSchema": "Device[]"
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/devices/open",
      "title": "Open an output device",
      "category": "Audio devices and engine cues",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "name",
          "string",
          "No",
          "Empty",
          "Empty opens default device; otherwise matches a device name."
        ],
        [
          "channels",
          "integer",
          "No",
          "2",
          "Requested channel count."
        ]
      ],
      "request": {},
      "response": {
        "device_id": "device-id"
      },
      "notes": "Engine-level operation. Prefer logical output mapping for project routing. Unknown names may fall back to the default device in this low-level API.",
      "errors": [
        [
          "400",
          "device open failed"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 2802
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/devices/close",
      "title": "Close an output device",
      "category": "Audio devices and engine cues",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "id",
          "string",
          "Yes",
          "-",
          "Engine device id."
        ]
      ],
      "request": {
        "id": "device-id"
      },
      "response": {
        "ok": true
      },
      "notes": "",
      "errors": [
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 2816
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/cues",
      "title": "List engine cues",
      "category": "Audio devices and engine cues",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": [
        {
          "id": "cue-id",
          "display_name": "Opening",
          "file_path": "C:/Shows/Show/media/opening.wav",
          "artist": "",
          "title": "",
          "duration_sec": 30,
          "gain_db": 0,
          "fade_in_ms": 0,
          "fade_out_ms": 0,
          "ltc": {
            "enabled": false,
            "fps": 4,
            "offset_ns": 0,
            "start_timecode": "00:00:00:00"
          },
          "transport": 0,
          "playhead_seconds": 0,
          "source_channels": 2,
          "file_loaded": true
        }
      ],
      "notes": "Engine IDs are separate from project UUIDs. Preview uses its own transient cue.",
      "errors": [],
      "sourceLine": 2826,
      "responseSchema": "Cue[]"
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/cues",
      "title": "Load an engine cue from disk",
      "category": "Audio devices and engine cues",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "file_path",
          "string",
          "Yes",
          "-",
          "Path on the server filesystem."
        ],
        [
          "display_name",
          "string",
          "No",
          "Empty",
          "Empty derives a display name."
        ]
      ],
      "request": {
        "file_path": "C:/Shows/Show/media/opening.wav"
      },
      "response": {
        "id": "cue-id",
        "display_name": "Opening",
        "file_path": "C:/Shows/Show/media/opening.wav",
        "artist": "",
        "title": "",
        "duration_sec": 30,
        "gain_db": 0,
        "fade_in_ms": 0,
        "fade_out_ms": 0,
        "ltc": {
          "enabled": false,
          "fps": 4,
          "offset_ns": 0,
          "start_timecode": "00:00:00:00"
        },
        "transport": 0,
        "playhead_seconds": 0,
        "source_channels": 2,
        "file_loaded": true
      },
      "notes": "Creates an engine cue, not a playlist item. This handler does not call the filesystem-root guard. Deduplicates by canonical file path; can return an existing project cue.",
      "errors": [
        [
          "400",
          "failed to load file"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 2833,
      "responseSchema": "Cue"
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/cues/<string>",
      "title": "Read an engine cue",
      "category": "Audio devices and engine cues",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "id": "cue-id",
        "display_name": "Opening",
        "file_path": "C:/Shows/Show/media/opening.wav",
        "artist": "",
        "title": "",
        "duration_sec": 30,
        "gain_db": 0,
        "fade_in_ms": 0,
        "fade_out_ms": 0,
        "ltc": {
          "enabled": false,
          "fps": 4,
          "offset_ns": 0,
          "start_timecode": "00:00:00:00"
        },
        "transport": 0,
        "playhead_seconds": 0,
        "source_channels": 2,
        "file_loaded": true
      },
      "notes": "Path: engine cue id.",
      "errors": [
        [
          "404",
          "not found"
        ]
      ],
      "sourceLine": 2846,
      "responseSchema": "Cue"
    },
    {
      "methods": [
        "DELETE"
      ],
      "path": "/api/cues/<string>",
      "title": "Remove an engine cue",
      "category": "Audio devices and engine cues",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "ok": true
      },
      "notes": "Path: engine cue id. Does not serve as the project-item deletion API. Project-owned cues are retained even though this route returns 200.",
      "errors": [
        [
          "404",
          "not found"
        ]
      ],
      "sourceLine": 2853
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/cues/<string>/play",
      "title": "Play an engine cue",
      "category": "Audio devices and engine cues",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "ok": true
      },
      "notes": "Path: engine cue id. Project-associated cues use play_item (including project behavior); engine-only cues use engine.play. This differs from item trigger/toggle semantics.",
      "errors": [
        [
          "404",
          "not found"
        ]
      ],
      "sourceLine": 2868
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/cues/<string>/stop",
      "title": "Stop an engine cue",
      "category": "Audio devices and engine cues",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "fade_ms",
          "number (ms)",
          "No",
          "Cue manual-stop fade",
          "Nonnegative after truncation/clamping; 0 cuts."
        ]
      ],
      "request": {
        "fade_ms": 0
      },
      "response": {
        "ok": true
      },
      "notes": "Path: engine cue id. Empty body allowed. For mapped project cues, uses project stop_item.",
      "errors": [
        [
          "404",
          "not found"
        ],
        [
          "400",
          "body must be JSON"
        ]
      ],
      "sourceLine": 2880
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/cues/<string>/gain",
      "title": "Set cue gain",
      "category": "Audio devices and engine cues",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "db",
          "number (dB)",
          "No",
          "0",
          "Engine cue gain."
        ]
      ],
      "request": {
        "db": -6
      },
      "response": {
        "ok": true
      },
      "notes": "Path: engine cue id. Unknown cue is not rejected by the route.",
      "errors": [
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 2901
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/cues/<string>/fade",
      "title": "Set cue fade times",
      "category": "Audio devices and engine cues",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "in_ms",
          "integer (ms)",
          "No",
          "0",
          "Fade-in."
        ],
        [
          "out_ms",
          "integer (ms)",
          "No",
          "0",
          "Fade-out."
        ]
      ],
      "request": {
        "in_ms": 250,
        "out_ms": 1000
      },
      "response": {
        "ok": true
      },
      "notes": "Path: engine cue id. Unknown cue is not rejected by the route.",
      "errors": [
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 2910
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/cues/<string>/ltc",
      "title": "Configure cue timecode",
      "category": "Audio devices and engine cues",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "enabled",
          "boolean",
          "No",
          "false",
          "Enable LTC."
        ],
        [
          "fps",
          "integer",
          "No",
          "4",
          "0=24, 1=25, 2=29.97 NDF, 3=29.97 DF, 4=30."
        ],
        [
          "offset_ns",
          "integer (ns)",
          "No",
          "0",
          "Start offset if no string timecode supplied."
        ],
        [
          "start_timecode",
          "string",
          "No",
          "00:00:00:00",
          "HH:MM:SS:FF or HH:MM:SS;FF; supplied string overrides offset_ns."
        ]
      ],
      "request": {
        "enabled": true,
        "fps": 1,
        "start_timecode": "01:00:00:00"
      },
      "response": {
        "ok": true
      },
      "notes": "Path: engine cue id. Timecode is converted using nominal frame counts and rate; malformed text is not explicitly rejected. LTC output destination is project settings.ltcOutput.",
      "errors": [
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 2922
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/master/ceiling",
      "title": "Set live master limiter ceiling",
      "category": "Master and preview",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "db",
          "number (dB)",
          "No",
          "-0.3",
          "Ceiling."
        ]
      ],
      "request": {
        "db": -1
      },
      "response": {
        "ok": true
      },
      "notes": "Live engine control; project output-target settings can apply their own ceiling. No doc_patch emitted here.",
      "errors": [
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 3202
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/master/gain",
      "title": "Read master gain",
      "category": "Master and preview",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "db": 0
      },
      "notes": "",
      "errors": [],
      "sourceLine": 3211
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/master/gain",
      "title": "Set or adjust master gain",
      "category": "Master and preview",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "db",
          "number (dB)",
          "One of",
          "-",
          "Absolute gain; takes precedence."
        ],
        [
          "delta",
          "number (dB)",
          "One of",
          "-",
          "Added to current gain."
        ]
      ],
      "request": {
        "delta": -1
      },
      "response": {
        "ok": true,
        "db": -1
      },
      "notes": "Returns effective engine value. Broadcasts master_gain_changed.",
      "errors": [
        [
          "400",
          "body needs a numeric \"db\" or \"delta\""
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 3213
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/master/limiter",
      "title": "Read live limiter state",
      "category": "Master and preview",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "enabled": true
      },
      "notes": "",
      "errors": [],
      "sourceLine": 3240
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/master/limiter",
      "title": "Set or toggle live limiter",
      "category": "Master and preview",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "enabled",
          "boolean",
          "No",
          "Toggle",
          "Non-boolean or missing toggles."
        ]
      ],
      "request": {
        "enabled": true
      },
      "response": {
        "ok": true,
        "enabled": true
      },
      "notes": "Empty body allowed. Broadcasts limiter_changed. Project disableLimiter is the persisted setting.",
      "errors": [
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 3242
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/master/channels/<int>/gain",
      "title": "Read a hardware-output channel trim",
      "category": "Master and preview",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "channel": 0,
        "db": 0
      },
      "notes": "Path: zero-based master channel index. Route rejects negative indices but does not validate the configured upper bound.",
      "errors": [
        [
          "400",
          "invalid channel index"
        ]
      ],
      "sourceLine": 3261
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/master/channels/<int>/gain",
      "title": "Set a hardware-output channel trim",
      "category": "Master and preview",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "db",
          "number (dB)",
          "No",
          "0",
          "Channel trim."
        ]
      ],
      "request": {
        "db": -3
      },
      "response": {
        "ok": true,
        "channel": 0,
        "db": -3
      },
      "notes": "Path: zero-based master channel index. Broadcasts output_channel_gain_changed. Upper-bound checking is left to the engine.",
      "errors": [
        [
          "400",
          "invalid channel index"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 3269
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/preview",
      "title": "Read pre-listen state",
      "category": "Master and preview",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "active": false,
        "itemUuid": "",
        "cueId": ""
      },
      "notes": "",
      "errors": [],
      "sourceLine": 4964
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/preview",
      "title": "Pre-listen to an item",
      "category": "Master and preview",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "itemUuid",
          "string",
          "Yes",
          "-",
          "Project audio item UUID."
        ]
      ],
      "request": {
        "itemUuid": "opening"
      },
      "response": {
        "ok": true,
        "itemUuid": "opening",
        "cueId": "preview-cue"
      },
      "notes": "Uses a separate cue through the Preview-role bus. Replaces any current preview; does not trigger show sequencing. A successful response does not prove Preview is mapped to audible hardware. Broadcasts preview_started.",
      "errors": [
        [
          "400",
          "itemUuid required"
        ],
        [
          "400",
          "preview could not start (item not found, or the Preview bus has no strip)"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 4974
    },
    {
      "methods": [
        "DELETE"
      ],
      "path": "/api/preview",
      "title": "Stop pre-listen",
      "category": "Master and preview",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "ok": true
      },
      "notes": "Immediate cut. Broadcasts preview_stopped if a preview existed.",
      "errors": [],
      "sourceLine": 5012
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/preview/mono",
      "title": "Set Preview mono audition",
      "category": "Master and preview",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "mono",
          "boolean",
          "No",
          "false",
          "Enable mono sum."
        ]
      ],
      "request": {
        "mono": true
      },
      "response": {
        "ok": true,
        "mono": true
      },
      "notes": "Both paths are supported aliases. 409 if Preview has no strip; invalid JSON/type: 400. Broadcast op remains monitor_mono_changed. Session state, not stored in the show.",
      "errors": [
        [
          "409",
          "the Preview bus has no strip"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 3502,
      "sections": [
        {
          "title": "Stereo image, bass mono and pan",
          "notes": [],
          "examples": [
            {
              "title": "Enable transient mono audition on the Preview bus",
              "body": {
                "mono": true
              }
            }
          ]
        }
      ]
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/monitor/mono",
      "title": "Set Preview mono audition",
      "category": "Master and preview",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "mono",
          "boolean",
          "No",
          "false",
          "Enable mono sum."
        ]
      ],
      "request": {
        "mono": true
      },
      "response": {
        "ok": true,
        "mono": true
      },
      "notes": "Both paths are supported aliases. 409 if Preview has no strip; invalid JSON/type: 400. Broadcast op remains monitor_mono_changed. Session state, not stored in the show.",
      "errors": [
        [
          "409",
          "the Preview bus has no strip"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 3501
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/buses",
      "title": "List buses and their DSP settings",
      "category": "Buses, DSP and outputs",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": [
        {
          "id": "master",
          "name": "Master",
          "color": "",
          "order": 0,
          "width": 2,
          "gainDb": 0,
          "mute": false,
          "pan": 0,
          "dsp": {},
          "pfl": false,
          "monoCheck": false,
          "bound": true,
          "outputDevices": [
            "Speakers"
          ],
          "master": true,
          "preview": false,
          "masters": [
            0,
            1
          ],
          "output": {
            "type": "output",
            "target": "Main Out"
          },
          "sends": [],
          "mixerId": "mixer-id",
          "itemUuids": [
            "opening"
          ]
        }
      ],
      "notes": "Example abbreviates dsp; the response contains the full DSP object. Find Master and Preview by role flags, not fixed IDs. DSP is read as part of this bus response; there is no separate DSP GET route. Values are stored settings, not uncommitted live adjustments.",
      "errors": [
        [
          "500",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 3292,
      "responseSchema": "Bus[]",
      "relatedSchemas": [
        "BusDsp"
      ],
      "links": [
        [
          "Update stored bus settings",
          "#http-patch-api-buses-string"
        ]
      ]
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/buses/<string>",
      "title": "Read bus settings and DSP",
      "category": "Buses, DSP and outputs",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "id": "master",
        "name": "Master",
        "color": "",
        "order": 0,
        "width": 2,
        "gainDb": 0,
        "mute": false,
        "pan": 0,
        "dsp": {},
        "pfl": false,
        "monoCheck": false,
        "bound": true,
        "outputDevices": [
          "Speakers"
        ],
        "master": true,
        "preview": false,
        "masters": [
          0,
          1
        ],
        "output": {
          "type": "output",
          "target": "Main Out"
        },
        "sends": [],
        "mixerId": "mixer-id",
        "itemUuids": [
          "opening"
        ]
      },
      "notes": "Path: bus id. dsp abbreviated in example. DSP is read as part of this bus response; there is no separate DSP GET route. Values are stored settings, not uncommitted live adjustments.",
      "errors": [
        [
          "404",
          "not found"
        ],
        [
          "500",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 3303,
      "responseSchema": "Bus",
      "relatedSchemas": [
        "BusDsp"
      ],
      "links": [
        [
          "Update stored bus settings",
          "#http-patch-api-buses-string"
        ]
      ]
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/buses",
      "title": "Create a project bus",
      "category": "Buses, DSP and outputs",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "name",
          "string",
          "No",
          "Bus",
          "Display name; server generates unique id."
        ],
        [
          "color",
          "string",
          "No",
          "Empty",
          "Display color."
        ],
        [
          "order",
          "integer",
          "No",
          "Last ordinary bus + 1",
          "Display order."
        ],
        [
          "width",
          "integer",
          "No",
          "2",
          "Clamped to 1..2."
        ],
        [
          "gainDb",
          "number (dB)",
          "No",
          "0",
          "Fader level."
        ],
        [
          "mute",
          "boolean",
          "No",
          "false",
          "Mute."
        ],
        [
          "pan",
          "number",
          "No",
          "0",
          "Clamped to -1..1."
        ],
        [
          "output",
          "BusOutput",
          "No",
          "Master-role bus",
          "Destination; see shared schema."
        ]
      ],
      "request": {
        "name": "Music",
        "output": {
          "type": "bus",
          "target": "master"
        }
      },
      "response": {
        "id": "music"
      },
      "notes": "Creation does not read dsp, sends, master, or preview; apply those with PATCH after creation. Invalid bus target/routing: 409; strip capacity: 507.",
      "errors": [
        [
          "409",
          "Invalid bus target, role constraint or routing cycle; reason in error."
        ],
        [
          "507",
          "no mixer strip available"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 3313
    },
    {
      "methods": [
        "PATCH"
      ],
      "path": "/api/buses/<string>",
      "title": "Update bus DSP, gain and routing",
      "category": "Buses, DSP and outputs",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "(body)",
          "Bus (writable fields)",
          "Yes",
          "-",
          "See shared Bus schema; all writable fields optional."
        ]
      ],
      "request": {
        "gainDb": -6,
        "sends": [
          {
            "id": "foldback",
            "levelDb": -12,
            "tap": "pre"
          }
        ]
      },
      "response": {
        "ok": true
      },
      "notes": "Path: bus id. Applies/persists into the in-memory project; use project/save for disk. Invalid targets, cycles or role changes: 409. Sends array replaces all sends. Broadcasts buses_patched; materializing a present hardware name can also emit outputs_changed.",
      "errors": [
        [
          "404",
          "not found"
        ],
        [
          "409",
          "Invalid bus target, role constraint or routing cycle; reason in error."
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 3333,
      "relatedSchemas": [
        "BusDsp"
      ],
      "links": [
        [
          "Bus change notification",
          "#patch-buses-patched"
        ],
        [
          "Save the project file",
          "#http-post-api-project-save"
        ]
      ],
      "sections": [
        {
          "title": "DSP updates",
          "notes": [
            "PATCH /api/buses/<id> accepts a dsp wrapper and merges the supplied fields into the in-memory project while applying them to the engine. POST /api/buses/<id>/dsp accepts the DSP object directly and applies a temporary engine-only change. The method entries below specify their authentication, responses and errors.",
            "A successful PATCH returns {\"ok\":true} and broadcasts doc_patch with op \"buses_patched\", containing all stored bus definitions and their DSP. HTTP response and WebSocket event ordering is not guaranteed. PATCH does not itself save the project file; POST /api/project/save writes the current document after a project path is set.",
            "POST /api/buses does not apply DSP settings during bus creation; use PATCH afterward. Defaults in the field tables describe a fresh bus. Omitted update fields retain stored values. Unknown keys are ignored; null is not a reset operation. Type errors can return 400, and partial mutation has no transactional rollback guarantee."
          ],
          "examples": [
            {
              "title": "Change one compressor parameter, retaining its other values",
              "body": {
                "dsp": {
                  "comp": {
                    "threshold": -22
                  }
                }
              }
            },
            {
              "title": "Commit that change and broadcast it",
              "body": {
                "dsp": {
                  "comp": {
                    "on": true,
                    "threshold": -24,
                    "ratio": 4
                  }
                }
              }
            }
          ]
        },
        {
          "title": "EQ bands: frequency, gain, Q, type, add and remove",
          "notes": [
            "Slots are zero-based. To change band 2, send two empty objects then its patch. Empty objects retain existing slots. A shorter array leaves later bands unchanged; eq:[] clears nothing. Do not splice an interior band out of your cache and resend the shifted array.",
            "Append at the current eq.length (maximum 32), or reuse an inactive interior slot with on:true. Extending across unused indices creates default enabled bands in the gaps; avoid this by using existing slots or appending exactly one.",
            "Disable with on:false. The server trims all disabled slots at the array tail, so re-fetch or consume the next buses_patched before assigning another slot. Interior disabled slots remain. To clear every band, send on:false at every current slot.",
            "Use type for new integrations. Serialized bands include shelf for compatibility: do not echo a stale shelf:false alongside type:\"lowShelf\" or type:\"highShelf\", because shelf is applied afterward and changes it back to bell. Send only the fields being edited, or remove shelf from a full read-modify-write payload.",
            "Bell uses freq/gain/q; shelves use freq/gain/slope; lowCut/highCut/notch use freq/q and ignore gain. Separate HPF/LPF controls are independent of cut bands. eqEnabled bypasses the band array while retaining its settings."
          ],
          "schema": "BusDsp",
          "fieldPrefixes": [
            "eq"
          ],
          "examples": [
            {
              "title": "Set band 2 (third slot) to a bell",
              "body": {
                "dsp": {
                  "eq": [
                    {},
                    {},
                    {
                      "on": true,
                      "type": "bell",
                      "freq": 2500,
                      "gain": -3,
                      "q": 1.4
                    }
                  ]
                }
              }
            },
            {
              "title": "Make band 0 a low shelf; omit the compatibility shelf field",
              "body": {
                "dsp": {
                  "eq": [
                    {
                      "type": "lowShelf",
                      "freq": 120,
                      "gain": 3,
                      "slope": 1
                    }
                  ]
                }
              }
            },
            {
              "title": "Make band 1 a notch",
              "body": {
                "dsp": {
                  "eq": [
                    {},
                    {
                      "type": "notch",
                      "freq": 315,
                      "q": 8,
                      "on": true
                    }
                  ]
                }
              }
            },
            {
              "title": "Append a fifth band when current eq.length is 4",
              "body": {
                "dsp": {
                  "eq": [
                    {},
                    {},
                    {},
                    {},
                    {
                      "type": "highCut",
                      "freq": 12000,
                      "q": 0.7071,
                      "on": true
                    }
                  ]
                }
              }
            },
            {
              "title": "Disable band 1 without shifting following slots",
              "body": {
                "dsp": {
                  "eq": [
                    {},
                    {
                      "on": false
                    }
                  ]
                }
              }
            },
            {
              "title": "Clear a bus that currently has exactly four slots",
              "body": {
                "dsp": {
                  "eq": [
                    {
                      "on": false
                    },
                    {
                      "on": false
                    },
                    {
                      "on": false
                    },
                    {
                      "on": false
                    }
                  ]
                }
              }
            }
          ]
        },
        {
          "title": "Separate high-pass and low-pass filters",
          "notes": [
            "Each is a single biquad with freq and q. There is no on, type, gain, slope or selectable filter-order field for these two sections. EQ shelf slope is not a filter steepness control.",
            "HPF processes above 20 Hz; LPF processes below 20000 Hz, with nonpositive values interpreted as their parked defaults. Bypass by setting HPF to 20 and LPF to 20000. eqEnabled does not bypass these filters.",
            "The API stores their frequency as supplied rather than clamping it. Engine coefficient design clamps frequency to its sample-rate limits (upper limit 0.49 × sample rate), so a readback is a stored setting, not necessarily the effective corner frequency."
          ],
          "schema": "BusDsp",
          "fieldPrefixes": [
            "hpf.",
            "lpf."
          ],
          "examples": [
            {
              "title": "High-pass at 80 Hz and low-pass at 16 kHz",
              "body": {
                "dsp": {
                  "hpf": {
                    "freq": 80,
                    "q": 0.7071
                  },
                  "lpf": {
                    "freq": 16000,
                    "q": 0.7071
                  }
                }
              }
            },
            {
              "title": "Bypass both separate filters",
              "body": {
                "dsp": {
                  "hpf": {
                    "freq": 20
                  },
                  "lpf": {
                    "freq": 20000
                  }
                }
              }
            }
          ]
        },
        {
          "title": "Expander / gate",
          "notes": [
            "The gate object is the downward expander/gate; there is no separate expander object. The detector is stereo-linked. Threshold, ratio and attenuation range set the expansion behavior.",
            "Both dynEnabled and gate.on must be true. A ratio of 1 or range of 0 also makes the processor inactive. Attack opens the gate; release closes it. Read gate_gr_db from the bus mixer meter for actual reduction."
          ],
          "schema": "BusDsp",
          "fieldPrefixes": [
            "gate."
          ],
          "examples": [
            {
              "title": "Enable an expander/gate with explicit timing",
              "body": {
                "dsp": {
                  "dynEnabled": true,
                  "gate": {
                    "on": true,
                    "threshold": -45,
                    "ratio": 3,
                    "range": -30,
                    "attack": 2,
                    "hold": 40,
                    "release": 180
                  }
                }
              }
            },
            {
              "title": "Change threshold alone",
              "body": {
                "dsp": {
                  "gate": {
                    "threshold": -38
                  }
                }
              }
            },
            {
              "title": "Bypass the gate, keeping the compressor available",
              "body": {
                "dsp": {
                  "gate": {
                    "on": false
                  }
                }
              }
            }
          ]
        },
        {
          "title": "Compressor / bus limiting",
          "notes": [
            "Both dynEnabled and comp.on must be true. Ratio 1 with zero makeup bypasses processing; ratio 1 with nonzero makeup is still an active gain stage.",
            "The detector is stereo-linked; compression uses a soft knee when knee >0. Attack increases attenuation and release recovers it. A high ratio is available, but this bus compressor has no lookahead and is separate from the master brick-wall limiter."
          ],
          "schema": "BusDsp",
          "fieldPrefixes": [
            "comp."
          ],
          "examples": [
            {
              "title": "Enable compression with every control explicit",
              "body": {
                "dsp": {
                  "dynEnabled": true,
                  "comp": {
                    "on": true,
                    "threshold": -18,
                    "ratio": 4,
                    "makeup": 2,
                    "attack": 10,
                    "knee": 6,
                    "release": 200
                  }
                }
              }
            },
            {
              "title": "Change ratio and makeup only",
              "body": {
                "dsp": {
                  "comp": {
                    "ratio": 2.5,
                    "makeup": 1
                  }
                }
              }
            },
            {
              "title": "Bypass the compressor, keeping the gate available",
              "body": {
                "dsp": {
                  "comp": {
                    "on": false
                  }
                }
              }
            }
          ]
        },
        {
          "title": "Stereo image, bass mono and pan",
          "notes": [
            "dsp.width.width controls the stereo image. Top-level bus.width is channel count (1 or 2); these are different controls. Stereo image processing is ignored for mono buses.",
            "There is no width.on switch. Width 1 with bassMonoHz 20 is neutral; width 0 folds to mono. Bass-mono filtering attenuates low-frequency side content rather than imposing a sharp crossover.",
            "Pan/balance is top-level bus.pan, not inside dsp. It clamps to -1..1. Preview mono-check overrides the preview-role bus image to mono without changing its stored DSP. dynEnabled and eqEnabled do not bypass stereo image processing."
          ],
          "schema": "BusDsp",
          "fieldPrefixes": [
            "width."
          ],
          "examples": [
            {
              "title": "Widen the image and narrow low-frequency side content",
              "body": {
                "dsp": {
                  "width": {
                    "width": 1.25,
                    "bassMonoHz": 120,
                    "bassMonoQ": 0.7071
                  }
                }
              }
            },
            {
              "title": "Restore neutral stereo image",
              "body": {
                "dsp": {
                  "width": {
                    "width": 1,
                    "bassMonoHz": 20
                  }
                }
              }
            },
            {
              "title": "Set bus pan/balance separately",
              "body": {
                "pan": -0.25
              }
            }
          ]
        },
        {
          "title": "Section bypass",
          "notes": [
            "There is no global DSP bypass field. eqEnabled controls EQ bands; dynEnabled controls gate and compressor. Separate filters and stereo width each use their neutral/parked values.",
            "Bypass preserves the settings within the bypassed EQ or dynamics section. The separate filters and stereo image have no independent bypass switches: setting them to neutral values changes their stored parameters. Restoring an earlier full DSP object can overwrite concurrent edits."
          ],
          "schema": "BusDsp",
          "fieldPrefixes": [
            "eqEnabled",
            "dynEnabled"
          ],
          "examples": [
            {
              "title": "Bypass EQ bands only; HPF/LPF remain active",
              "body": {
                "dsp": {
                  "eqEnabled": false
                }
              }
            },
            {
              "title": "Bypass both dynamics processors",
              "body": {
                "dsp": {
                  "dynEnabled": false
                }
              }
            },
            {
              "title": "Restore the section switches without losing their settings",
              "body": {
                "dsp": {
                  "eqEnabled": true,
                  "dynEnabled": true
                }
              }
            }
          ]
        }
      ]
    },
    {
      "methods": [
        "DELETE"
      ],
      "path": "/api/buses/<string>",
      "title": "Delete an ordinary bus",
      "category": "Buses, DSP and outputs",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "ok": true
      },
      "notes": "Path: bus id. Cannot delete a role holder (409); move the role first. Reconciles referring routes/items and broadcasts buses_patched.",
      "errors": [
        [
          "404",
          "not found"
        ],
        [
          "409",
          "Bus is not deletable (including a role holder); reason in error."
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 3504
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/buses/<string>/pan",
      "title": "Adjust live pan/balance",
      "category": "Buses, DSP and outputs",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "pan",
          "number",
          "No",
          "0",
          "Clamped to -1..1."
        ]
      ],
      "request": {
        "pan": 0.25
      },
      "response": {
        "ok": true
      },
      "notes": "Path: bus id. Does not persist or broadcast. PATCH pan after a gesture to store the value.",
      "errors": [
        [
          "404",
          "not found"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 3373
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/buses/<string>/dsp",
      "title": "Adjust temporary DSP (EQ, filters, gate, compressor, stereo image)",
      "category": "Buses, DSP and outputs",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "(body)",
          "BusDsp partial object",
          "Yes",
          "-",
          "The DSP object itself; no dsp wrapper."
        ]
      ],
      "request": {
        "comp": {
          "on": true,
          "threshold": -20,
          "ratio": 4
        }
      },
      "response": {
        "ok": true
      },
      "notes": "Path: bus id. Does not persist or broadcast. Each call merges against stored DSP, not the preceding live adjustment. Include every uncommitted modification in each call; then PATCH with a dsp wrapper to store. GET still returns stored DSP. POST an empty object to restore stored DSP to the engine. No WebSocket DSP write command exists.",
      "errors": [
        [
          "404",
          "not found"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 3390,
      "sections": [
        {
          "title": "Temporary DSP updates",
          "notes": [
            "The request body is the DSP object itself, without a dsp wrapper. This method changes the engine only: it does not update the stored project or broadcast buses_patched. GET continues to return stored DSP.",
            "Each request merges against stored DSP, not against the preceding temporary adjustment. For example, sending an EQ change followed by only a compressor change restores EQ to its stored value. Every temporary request must include all desired uncommitted changes to retain them.",
            "PATCH /api/buses/<id> with {\"dsp\": {...}} stores and applies the supplied changes. POST /api/buses/<id>/dsp with {} reapplies stored DSP to the engine. Serialized EQ shelf flags must agree with type or be omitted."
          ],
          "examples": [
            {
              "title": "Live-only change: raw DSP body",
              "body": {
                "comp": {
                  "on": true,
                  "threshold": -24,
                  "ratio": 4
                }
              }
            },
            {
              "title": "Cancel uncommitted live DSP and restore stored values",
              "body": {}
            }
          ]
        }
      ],
      "relatedSchemas": [
        "BusDsp"
      ],
      "links": [
        [
          "Update stored bus settings",
          "#http-patch-api-buses-string"
        ],
        [
          "Bus change notification (stored edits only)",
          "#patch-buses-patched"
        ]
      ]
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/buses/<string>/pfl",
      "title": "Set pre-fade listen",
      "category": "Buses, DSP and outputs",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "pfl",
          "boolean",
          "No",
          "false",
          "Listen to this bus through Preview."
        ]
      ],
      "request": {
        "pfl": true
      },
      "response": {
        "ok": true,
        "pfl": true
      },
      "notes": "Path: bus id. Preview itself cannot be PFL'd. Broadcasts bus_pfl_changed; not persisted.",
      "errors": [
        [
          "404",
          "not found, or not a bus that can be PFL'd"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 3417
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/buses/pfl/clear",
      "title": "Clear all PFL taps",
      "category": "Buses, DSP and outputs",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "cleared": 2
      },
      "notes": "Broadcasts bus_pfl_cleared only when at least one tap was cleared.",
      "errors": [
        [
          "500",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 3404
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/outputs",
      "title": "Read server logical-output mappings",
      "category": "Buses, DSP and outputs",
      "auth": "Administrator",
      "apiToken": "No",
      "fields": [],
      "request": null,
      "response": {
        "version": 1,
        "outputs": [
          {
            "name": "Main Out",
            "channels": [
              {
                "device": "Speakers",
                "hwChannel": 0
              },
              {
                "device": "Speakers",
                "hwChannel": 1
              }
            ]
          }
        ],
        "builtin": [
          "Main Out",
          "Preview Out"
        ]
      },
      "notes": "Server-owned, separate from the project.",
      "errors": [
        [
          "500",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 3525,
      "responseSchema": "OutputMap"
    },
    {
      "methods": [
        "PUT"
      ],
      "path": "/api/outputs",
      "title": "Replace logical-output mappings",
      "category": "Buses, DSP and outputs",
      "auth": "Administrator",
      "apiToken": "No",
      "fields": [
        [
          "outputs",
          "OutputMapping[]",
          "Yes",
          "-",
          "Complete map; see shared OutputMap schema."
        ],
        [
          "version",
          "integer",
          "No",
          "1",
          "Serialized version is 1; input version is not validated."
        ]
      ],
      "request": {
        "version": 1,
        "outputs": [
          {
            "name": "Main Out",
            "channels": [
              {
                "device": "Speakers",
                "hwChannel": 0
              },
              {
                "device": "Speakers",
                "hwChannel": 1
              }
            ]
          }
        ]
      },
      "response": {
        "version": 1,
        "outputs": [
          {
            "name": "Main Out",
            "channels": [
              {
                "device": "Speakers",
                "hwChannel": 0
              },
              {
                "device": "Speakers",
                "hwChannel": 1
              }
            ]
          }
        ],
        "rewiredBuses": 1
      },
      "notes": "Replaces the entire map, saves outputs.json and rewires buses live. Invalid/empty entries are skipped. The handler does not check the save return value. Broadcasts outputs_changed.",
      "errors": [
        [
          "400",
          "malformed output map"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 3537,
      "responseSchema": "OutputMap"
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/mixers",
      "title": "List engine mixer strips",
      "category": "Low-level mixer and routing",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": [
        {
          "id": "mixer-id",
          "display_name": "Music",
          "gain_db": 0,
          "muted": false,
          "pfl": false
        }
      ],
      "notes": "Use project buses for portable show routing; these endpoints operate directly on the engine.",
      "errors": [],
      "sourceLine": 3564
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/mixers",
      "title": "Create an engine strip",
      "category": "Low-level mixer and routing",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "name",
          "string",
          "No",
          "Channel",
          "Display name."
        ]
      ],
      "request": {
        "name": "External strip"
      },
      "response": {
        "id": "mixer-id"
      },
      "notes": "No project Bus is created. 507 when strip limit is reached.",
      "errors": [
        [
          "507",
          "mixer channel limit reached"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 3584
    },
    {
      "methods": [
        "DELETE"
      ],
      "path": "/api/mixers/<string>",
      "title": "Remove an engine strip",
      "category": "Low-level mixer and routing",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": {
        "ok": true
      },
      "notes": "Path: engine mixer id.",
      "errors": [
        [
          "404",
          "not found"
        ]
      ],
      "sourceLine": 3595
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/mixers/<string>/gain",
      "title": "Set engine strip gain",
      "category": "Low-level mixer and routing",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "db",
          "number (dB)",
          "No",
          "0",
          "Live gain."
        ]
      ],
      "request": {
        "db": -6
      },
      "response": {
        "ok": true
      },
      "notes": "Path: engine mixer id; no project patch or broadcast.",
      "errors": [
        [
          "404",
          "not found"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 3606
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/mixers/<string>/mute",
      "title": "Set engine strip mute",
      "category": "Low-level mixer and routing",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "muted",
          "boolean",
          "No",
          "false",
          "Live mute."
        ]
      ],
      "request": {
        "muted": true
      },
      "response": {
        "ok": true
      },
      "notes": "Path: engine mixer id; note muted here versus mute in the bus API.",
      "errors": [
        [
          "404",
          "not found"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 3617
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/routing/item_to_mixer",
      "title": "Route a source channel into a strip",
      "category": "Low-level mixer and routing",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "cue",
          "string",
          "Yes",
          "-",
          "Engine cue id."
        ],
        [
          "mixer",
          "string",
          "Yes",
          "-",
          "Engine mixer id."
        ],
        [
          "source_channel",
          "integer",
          "No",
          "0",
          "Zero-based source channel."
        ],
        [
          "gain_db",
          "number (dB)",
          "No",
          "0",
          "Send level."
        ],
        [
          "lane",
          "integer",
          "No",
          "4294967295",
          "0 left, 1 right; sentinel sends to all lanes."
        ]
      ],
      "request": {
        "cue": "cue-id",
        "mixer": "mixer-id",
        "source_channel": 0,
        "lane": 0
      },
      "response": {
        "ok": true
      },
      "notes": "Live engine route; not a project bus assignment. Route does not validate target existence.",
      "errors": [
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 3629
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/routing/mixer_to_master",
      "title": "Route a strip into a master channel",
      "category": "Low-level mixer and routing",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "mixer",
          "string",
          "Yes",
          "-",
          "Engine mixer id."
        ],
        [
          "master_channel",
          "integer",
          "No",
          "0",
          "Zero-based master channel."
        ],
        [
          "gain_db",
          "number (dB)",
          "No",
          "0",
          "Send level."
        ],
        [
          "lane",
          "integer",
          "No",
          "4294967295",
          "0 left, 1 right; sentinel addresses all lanes."
        ]
      ],
      "request": {
        "mixer": "mixer-id",
        "master_channel": 0,
        "lane": 0
      },
      "response": {
        "ok": true
      },
      "notes": "Live engine routing; no project persistence or broadcast.",
      "errors": [
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 3645
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/routing/master_to_device",
      "title": "Assign a master channel to hardware",
      "category": "Low-level mixer and routing",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "device",
          "string",
          "Yes",
          "-",
          "Engine device id."
        ],
        [
          "master_channel",
          "integer",
          "No",
          "0",
          "Zero-based master channel."
        ],
        [
          "hw_channel",
          "integer",
          "No",
          "0",
          "Zero-based hardware channel."
        ]
      ],
      "request": {
        "device": "device-id",
        "master_channel": 0,
        "hw_channel": 0
      },
      "response": {
        "ok": true
      },
      "notes": "Live engine routing; prefer the logical output map for installation configuration.",
      "errors": [
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 3660
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/engine/stats",
      "title": "Read engine timing and drift diagnostics",
      "category": "Low-level mixer and routing",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "?reset",
          "any text",
          "No",
          "Absent",
          "Presence resets peak diagnostics, even reset=0."
        ]
      ],
      "request": null,
      "response": {
        "queuedFrames": 1536,
        "queuedMs": 32,
        "ringCapacityFrames": 1792,
        "devicePeriodFrames": 480,
        "devicePeriods": 3,
        "deviceMs": 30,
        "renderBlockUsMax": 1100,
        "renderBlockUsAvg": 110,
        "blockBudgetUs": 5333,
        "blocksRendered": 1000,
        "underruns": 0,
        "topologyRebuilds": 2,
        "mutexWaitUsMax": 10,
        "discontinuities": 0,
        "worstSeam": 0,
        "deviceCount": 1,
        "devices": [
          {
            "name": "Speakers",
            "isClock": true,
            "ppm": 0,
            "fillPercent": 50,
            "ringFillPercent": 80
          }
        ]
      },
      "notes": "Numbers in the example are illustrative, not latency guarantees.",
      "errors": [],
      "sourceLine": 3439,
      "responseSchema": "EngineStats"
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/fs/list",
      "title": "Browse files on the server",
      "category": "Filesystem and media",
      "auth": "Operator",
      "apiToken": "No",
      "fields": [
        [
          "?path",
          "string",
          "No",
          "Empty",
          "Empty lists computer roots; otherwise a server path."
        ],
        [
          "?filter",
          "string",
          "No",
          "audio",
          "audio, all, or comma-separated extensions (with or without dots)."
        ]
      ],
      "request": null,
      "response": {
        "path": "C:/Shows",
        "parent": "C:/",
        "is_root": false,
        "entries": [
          {
            "name": "Show",
            "full_path": "C:/Shows/Show",
            "kind": "dir"
          }
        ]
      },
      "notes": "Entries have name/full_path/kind; kind is home, drive, dir or file. File entries also have size in bytes. Dot-prefixed names are hidden. Empty path honors configured roots; normal paths are canonicalized and checked. Audio filter: wav/aiff/aif/flac/mp3/ogg/m4a/aac/opus/wma/caf; inclusion does not guarantee decode support.",
      "errors": [
        [
          "404",
          "no such path"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ],
        [
          "403",
          "Path is outside configured filesystem roots."
        ]
      ],
      "sourceLine": 3680,
      "responseSchema": "FileListing"
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/fs/mkdir",
      "title": "Create a server directory",
      "category": "Filesystem and media",
      "auth": "Operator",
      "apiToken": "No",
      "fields": [
        [
          "path",
          "string",
          "Yes",
          "-",
          "Nonempty path within permitted roots."
        ]
      ],
      "request": {
        "path": "C:/Shows/NewShow"
      },
      "response": {
        "path": "C:/Shows/NewShow"
      },
      "notes": "Creates parent directories as needed.",
      "errors": [
        [
          "400",
          "missing 'path'"
        ],
        [
          "400",
          "empty path"
        ],
        [
          "400",
          "Operation failed; diagnostic message in error. See behavioral notes."
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ],
        [
          "403",
          "Path is outside configured filesystem roots."
        ]
      ],
      "sourceLine": 3929
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/upload",
      "title": "Upload files into the media root",
      "category": "Filesystem and media",
      "auth": "Operator",
      "apiToken": "No",
      "fields": [
        [
          "multipart parts",
          "binary",
          "Yes",
          "-",
          "Each part is written; filename from Content-Disposition, default upload.bin."
        ]
      ],
      "request": {
        "multipart": "file=@opening.wav"
      },
      "response": {
        "saved": [
          "C:/Shows/Show/media/opening.wav"
        ]
      },
      "notes": "Content-Type: multipart/form-data with boundary. Use curl -F \"file=@opening.wav\". Total body size limited by maxUploadBytes. Uses basename; overwrites an existing same-named file. Does not create project items or queue waveforms.",
      "errors": [
        [
          "413",
          "payload too large"
        ],
        [
          "400",
          "no multipart parts"
        ],
        [
          "500",
          "failed to write file"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 3946
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/copy_to_media",
      "title": "Copy an existing server file into media",
      "category": "Filesystem and media",
      "auth": "Operator",
      "apiToken": "No",
      "fields": [
        [
          "source_path",
          "string",
          "Yes",
          "-",
          "Nonempty source path within permitted roots."
        ]
      ],
      "request": {
        "source_path": "C:/Audio/opening.wav"
      },
      "response": {
        "dest_path": "C:/Shows/Show/media/opening.wav"
      },
      "notes": "Overwrites the destination basename unless source and destination resolve to the same path. JSON parse/type and filesystem exceptions become 500 in this handler.",
      "errors": [
        [
          "400",
          "missing source_path"
        ],
        [
          "404",
          "source file not found"
        ],
        [
          "500",
          "media root not configured"
        ],
        [
          "500",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ],
        [
          "403",
          "Path is outside configured filesystem roots."
        ]
      ],
      "sourceLine": 3991
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/metadata",
      "title": "Read audio file tags and properties",
      "category": "Filesystem and media",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "?path",
          "string",
          "Yes",
          "-",
          "Server-side file path within permitted roots."
        ]
      ],
      "request": null,
      "response": {
        "valid": true,
        "artist": "",
        "title": "Opening",
        "album": "",
        "genre": "",
        "year": 0,
        "track_number": 0,
        "duration_ms": 30000,
        "sample_rate": 48000,
        "channels": 2,
        "bitrate_kbps": 1536
      },
      "notes": "Invalid/unreadable media is represented by valid=false rather than an explicit not-found status.",
      "errors": [
        [
          "400",
          "missing ?path="
        ],
        [
          "403",
          "Path is outside configured filesystem roots."
        ]
      ],
      "sourceLine": 4061,
      "responseSchema": "Metadata"
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/waveform/<string>",
      "title": "Compute a loaded cue's waveform",
      "category": "Filesystem and media",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "?buckets",
          "integer",
          "No",
          "1000",
          "Requested bucket count; parse failure retains default."
        ]
      ],
      "request": null,
      "response": {
        "cue_id": "cue-id",
        "bucket_count": 2,
        "duration_ms": 1000,
        "sample_rate": 48000,
        "source_channels": 1,
        "channels": [
          {
            "peak": [
              0.2,
              0.4
            ],
            "rms": [
              0.1,
              0.2
            ]
          }
        ]
      },
      "notes": "Path: engine cue id. Example uses buckets=2. Bucket values are linear amplitudes, not dB; channel arrays follow source channel order. No explicit range check at route boundary.",
      "errors": [
        [
          "404",
          "no such cue"
        ],
        [
          "500",
          "waveform decode failed"
        ],
        [
          "500",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ],
        [
          "500",
          "unknown error computing waveform"
        ]
      ],
      "sourceLine": 4083,
      "responseSchema": "Waveform"
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/waveform_path",
      "title": "Compute a waveform by file path",
      "category": "Filesystem and media",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "?path",
          "string",
          "Yes",
          "-",
          "Server file path within permitted roots."
        ],
        [
          "?buckets",
          "integer",
          "No",
          "1000",
          "Requested bucket count; parse failure retains default."
        ]
      ],
      "request": null,
      "response": {
        "bucket_count": 2,
        "duration_ms": 1000,
        "sample_rate": 48000,
        "source_channels": 1,
        "channels": [
          {
            "peak": [
              0.2,
              0.4
            ],
            "rms": [
              0.1,
              0.2
            ]
          }
        ]
      },
      "notes": "Synchronous decode. Example uses buckets=2. No cue_id field in this response.",
      "errors": [
        [
          "400",
          "missing path parameter"
        ],
        [
          "500",
          "waveform decode failed"
        ],
        [
          "500",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ],
        [
          "500",
          "unknown error computing waveform"
        ],
        [
          "403",
          "Path is outside configured filesystem roots."
        ]
      ],
      "sourceLine": 4117,
      "responseSchema": "Waveform"
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/waveform_generate",
      "title": "Queue asynchronous waveform generation",
      "category": "Filesystem and media",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [
        [
          "path",
          "string",
          "Yes",
          "-",
          "Nonempty server path within permitted roots."
        ],
        [
          "item_uuid",
          "string",
          "Yes",
          "-",
          "Nonempty identifier echoed in the result; also names cache file."
        ],
        [
          "force",
          "boolean",
          "No",
          "false",
          "Delete cached waveform before computing."
        ]
      ],
      "request": {
        "path": "C:/Shows/Show/media/opening.wav",
        "item_uuid": "opening"
      },
      "response": {
        "ok": true
      },
      "notes": "200 only acknowledges queueing. Receives doc_patch waveform_ready or waveform_failed later; queue is serial. Cached under project waveforms/<item_uuid>.json when a project path exists. Do not use path separators in identifiers. Worker exceptions can be logged without an event.",
      "errors": [
        [
          "400",
          "missing path or item_uuid"
        ],
        [
          "500",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ],
        [
          "403",
          "Path is outside configured filesystem roots."
        ]
      ],
      "sourceLine": 4029
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/project/export",
      "title": "Package a server folder as an LPA archive",
      "category": "Project archives",
      "auth": "Operator",
      "apiToken": "No",
      "fields": [
        [
          "folderPath",
          "string",
          "Yes",
          "-",
          "Existing server directory within permitted roots."
        ],
        [
          "projectName",
          "string",
          "No",
          "Folder basename",
          "Archive filename stem when using temporary output."
        ],
        [
          "outputPath",
          "string",
          "No",
          "Temporary export folder",
          "Explicit server destination within roots."
        ]
      ],
      "request": {
        "folderPath": "C:/Shows/Show",
        "projectName": "Show"
      },
      "response": {
        "archivePath": "C:/Temp/liveplay-exports/Show.lpa",
        "size": 123456,
        "downloadToken": "download-token",
        "downloadFilename": "Show.lpa"
      },
      "notes": "Packs folder contents, not an automatic save of in-memory edits. Without outputPath returns a single-use download token valid for 10 minutes. With outputPath, no downloadToken/downloadFilename fields. Do not put outputPath inside the source folder.",
      "errors": [
        [
          "400",
          "expected 'folderPath'"
        ],
        [
          "400",
          "folderPath does not exist or is not a directory"
        ],
        [
          "500",
          "failed to package archive"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ],
        [
          "403",
          "Path is outside configured filesystem roots."
        ]
      ],
      "sourceLine": 4313
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/api/file/download",
      "title": "Download a temporary exported archive",
      "category": "Project archives",
      "auth": "Operator",
      "apiToken": "No",
      "fields": [
        [
          "?token",
          "string",
          "Yes",
          "-",
          "Single-use token returned by export."
        ]
      ],
      "request": null,
      "response": "Binary .lpa bytes",
      "notes": "Also requires normal Bearer auth when login is enabled; this query token is not authentication. 200 application/octet-stream with Content-Disposition attachment. Redeems token and deletes temporary archive. Invalid/expired token: 404.",
      "errors": [
        [
          "400",
          "missing ?token="
        ],
        [
          "404",
          "token expired or invalid"
        ],
        [
          "500",
          "failed to open archive"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ]
      ],
      "sourceLine": 4378
    },
    {
      "methods": [
        "POST"
      ],
      "path": "/api/project/import",
      "title": "Extract an LPA archive",
      "category": "Project archives",
      "auth": "Operator",
      "apiToken": "No",
      "fields": [
        [
          "archivePath",
          "string",
          "JSON mode",
          "-",
          "Server archive path within roots."
        ],
        [
          "extractPath",
          "string",
          "Yes",
          "-",
          "Server extraction folder within roots."
        ],
        [
          "file",
          "multipart binary",
          "Multipart mode",
          "-",
          "Archive part named file; extractPath is a form field."
        ]
      ],
      "request": {
        "archivePath": "C:/Shows/Show.lpa",
        "extractPath": "C:/Shows/Imported"
      },
      "response": {
        "extractPath": "C:/Shows/Imported",
        "projectFiles": [
          "show.liveplay"
        ]
      },
      "notes": "JSON or multipart/form-data. Extracts but does not load the project. projectFiles lists only top-level .liveplay basenames. The multipart import handler does not enforce maxUploadBytes. Absolute and names containing .. are skipped by extraction logic.",
      "errors": [
        [
          "400",
          "missing 'file' part"
        ],
        [
          "400",
          "missing 'extractPath' form field"
        ],
        [
          "500",
          "failed to stage uploaded archive"
        ],
        [
          "400",
          "expected 'archivePath'"
        ],
        [
          "400",
          "expected 'extractPath'"
        ],
        [
          "400",
          "archive does not exist"
        ],
        [
          "500",
          "extract failed"
        ],
        [
          "400",
          "Handler exception (JSON parsing, type conversion or operation failure); message varies."
        ],
        [
          "403",
          "Path is outside configured filesystem roots."
        ]
      ],
      "sourceLine": 4422
    },
    {
      "methods": [
        "GET"
      ],
      "path": "/static/<path>",
      "title": "Read a framework static file",
      "category": "Framework HTTP behavior",
      "auth": "Operator",
      "apiToken": "Yes",
      "fields": [],
      "request": null,
      "response": "File bytes",
      "notes": "Crow registers this automatically at run(): serves sanitized relative names from static/ under the process working directory. It is not the public docs website and is not checked by LivePlay fsRoots. Missing files return framework 404; MIME type is based on extension. Example: GET /static/example.txt returns its contents only if that file exists. HEAD follows GET without a response body.",
      "errors": [
        [
          "404",
          "File does not exist or cannot be stat-ed"
        ]
      ],
      "source": "Crow app.h / settings.h / http_response.h (installed dependency)"
    }
  ],
  "schemas": [
    {
      "name": "ProjectItem",
      "description": "Project item objects are preserved as JSON; the server does not enforce a complete item schema. Use the following current control fields. On PATCH, omitted fields retain their values. Defaults below describe runtime fallbacks, which can differ from defaults supplied by the desktop importer.",
      "fields": [
        [
          "uuid",
          "string",
          "Create",
          "-",
          "Stable nonempty project identity."
        ],
        [
          "type",
          "string",
          "Create",
          "-",
          "audio or group are the playable item types; action is present in client types but has no general trigger implementation."
        ],
        [
          "displayName, color",
          "string",
          "No",
          "Empty",
          "Operator-facing label and CSS color."
        ],
        [
          "index",
          "integer[]",
          "No",
          "Derived from tree",
          "Zero-based playlist location; API by-index resolves tree order."
        ],
        [
          "busId",
          "string",
          "No",
          "Inherit",
          "Nearest ancestor busId, then Master-role bus; applies to groups and audio."
        ],
        [
          "cueId",
          "string",
          "Response only",
          "Absent if not mapped",
          "Engine identity for an audio item; can change when reloading."
        ],
        [
          "children",
          "ProjectItem[]",
          "Group",
          "[]",
          "Nested items."
        ],
        [
          "isExpanded",
          "boolean",
          "No",
          "Client presentation",
          "Group expansion."
        ],
        [
          "mediaPath",
          "string",
          "Audio",
          "Empty",
          "Path relative to project folder; prefer media/<filename>."
        ],
        [
          "mediaServerPath",
          "string",
          "No",
          "Empty",
          "Host-specific absolute fallback; existing relative mediaPath takes precedence."
        ],
        [
          "mediaFileName, waveformPath",
          "string",
          "No",
          "Empty",
          "Media/display/cache metadata."
        ],
        [
          "waveform",
          "object",
          "No",
          "Absent",
          "Client waveform cache: length integer, duration seconds, peaks number[], optional channelPeaks number[][]; not the REST Waveform shape."
        ],
        [
          "inPoint",
          "number (s)",
          "No",
          "0",
          "File start position."
        ],
        [
          "outPoint",
          "number (s)",
          "No",
          "0",
          "0 means file end."
        ],
        [
          "duration",
          "number (s)",
          "No",
          "0",
          "File duration metadata."
        ],
        [
          "volume",
          "number (linear)",
          "No",
          "Engine default",
          "1 is unity; client range 0..2; server maps <=0.0001 to -120 dB without enforcing the UI maximum."
        ],
        [
          "playFade, stopFade, crossFade",
          "number (s)",
          "No",
          "0",
          "Fade-in, end fade-out, transition overlap."
        ],
        [
          "manualStopFade",
          "number (s)",
          "No",
          "Compatibility rule",
          "Stop-button fade; otherwise max(stopFade, fadeOutDuration)."
        ],
        [
          "fadeOutDuration",
          "number (s)",
          "No",
          "0 in server; 1 supplied by desktop",
          "End fade is max(stopFade, fadeOutDuration); also manual-stop fallback."
        ],
        [
          "loopCrossfade",
          "number (s)",
          "No",
          "0",
          "Loop overlap, capped at half trimmed duration."
        ],
        [
          "advanceDelay",
          "number (s)",
          "No",
          "0",
          "Wait before next/goto; ignored with overlapping transitions."
        ],
        [
          "startNextEnabled",
          "boolean",
          "No",
          "false",
          "Enable an absolute segue marker."
        ],
        [
          "startNextTime",
          "number (s)",
          "No",
          "0",
          "Absolute file position that triggers next."
        ],
        [
          "startNextFadeOut",
          "boolean",
          "No",
          "false",
          "Begin outgoing fade at marker."
        ],
        [
          "endBehavior",
          "object",
          "No",
          "No action",
          "action: nothing/next/goto-item/goto-index/loop; targetUuid string or targetIndex integer[] as needed."
        ],
        [
          "startBehavior",
          "object",
          "No",
          "Type-specific",
          "Audio action: nothing/play-next/play-item/play-index, with targetUuid/targetIndex. Group: play-first/play-all."
        ],
        [
          "duckingBehavior",
          "object",
          "No",
          "No ducking",
          "mode: no-ducking/stop-all/duck-others; duckLevel linear number, duckFadeIn/duckFadeOut seconds."
        ],
        [
          "customActions",
          "object[]",
          "No",
          "[]",
          "Each has timePoint seconds and action object; see CustomAction."
        ],
        [
          "ltcEnabled",
          "boolean",
          "No",
          "false",
          "Enable cue LTC."
        ],
        [
          "ltcStartTimecode",
          "string",
          "No",
          "00:00:00:00",
          "Initial timecode."
        ],
        [
          "ltcFrameRate",
          "integer",
          "No",
          "4",
          "0=24, 1=25, 2=29.97NDF, 3=29.97DF, 4=30."
        ]
      ]
    },
    {
      "name": "CustomAction",
      "description": "The sequencer processes timed actions on project audio items. HTTP actions are sent to clients for execution; the server does not issue an outbound HTTP request.",
      "fields": [
        [
          "type",
          "string",
          "Yes",
          "-",
          "play-item, play-index, stop-all, http-request."
        ],
        [
          "uuid",
          "string",
          "play-item",
          "-",
          "Project UUID."
        ],
        [
          "index",
          "integer[]",
          "play-index",
          "-",
          "Zero-based tree indices."
        ],
        [
          "request",
          "object",
          "http-request",
          "-",
          "url string, method GET/POST/PUT/DELETE, contentType form/json, optional body object. Desktop executes; server does not validate."
        ]
      ]
    },
    {
      "name": "ProjectDocument",
      "description": "Full .liveplay-style document. GET adds engine annotations and server state; these response-only values are not a portable persistence contract. Inline documents should use items; the loader also accepts compatible engine-oriented files. Save through the API to apply persistence cleanup.",
      "fields": [
        [
          "name, version",
          "string",
          "No",
          "Empty",
          "Project label and document version."
        ],
        [
          "items",
          "ProjectItem[]",
          "Current document",
          "[]",
          "Top-level playlist; nested children stay in groups."
        ],
        [
          "cartOnlyItems",
          "ProjectItem[]",
          "No",
          "[]",
          "Audio items outside the playlist."
        ],
        [
          "cartItems",
          "object[]",
          "No",
          "[]",
          "slot integer (0..63), itemUuid string; server stores index [-1, slot]."
        ],
        [
          "cartSlotKeys",
          "object",
          "No",
          "{}",
          "Slot bindings: key string; ctrlKey/shiftKey/altKey booleans."
        ],
        [
          "settings",
          "ProjectSettings",
          "No",
          "{}",
          "Show settings."
        ],
        [
          "buses",
          "Bus[]",
          "No",
          "Generated role buses",
          "Persisted bus definitions; runtime fields absent."
        ],
        [
          "busSchema",
          "integer",
          "No",
          "Compatibility loader",
          "Current bus schema is 2."
        ],
        [
          "createdAt, lastModified",
          "string",
          "No",
          "Document value",
          "Client timestamps; preserved metadata."
        ],
        [
          "folderPath",
          "string",
          "Read derived",
          "Project file folder",
          "Computed from the loaded/saved .liveplay location; removed on disk."
        ],
        [
          "server",
          "object",
          "Response only",
          "-",
          "projectFilePath/mediaRoot strings; audioLoading bool; audioLoaded/audioTotal integers."
        ],
        [
          "theme, playbackKeys",
          "object",
          "Compatibility only",
          "{}",
          "Read for seeding user preferences; not maintained as project preferences."
        ]
      ]
    },
    {
      "name": "ProjectHeader",
      "description": "Returned by header/document/load. Does not include the playlist items or bus definitions. All listed header keys are emitted; load adds repair and optional migration fields.",
      "fields": [
        [
          "name, version, folderPath, createdAt, lastModified",
          "string",
          "Yes",
          "-",
          "Project metadata."
        ],
        [
          "theme, settings, cartSlotKeys, playbackKeys",
          "object",
          "Yes",
          "-",
          "settings is ProjectSettings; other maps may be empty."
        ],
        [
          "cartItems, cartOnlyItems",
          "array",
          "Yes",
          "-",
          "Same elements as ProjectDocument."
        ],
        [
          "itemCount",
          "integer",
          "Yes",
          "-",
          "Top-level item count."
        ],
        [
          "busSchema",
          "integer",
          "Yes",
          "-",
          "Current value normally 2."
        ],
        [
          "hasOpenProject",
          "boolean",
          "Yes",
          "-",
          "True when top-level items exist or a project file path is set."
        ],
        [
          "server",
          "object",
          "Yes",
          "-",
          "Same runtime metadata as ProjectDocument."
        ],
        [
          "migration",
          "Migration",
          "Conditional",
          "Absent",
          "Included by document/load when normalization reports changes."
        ]
      ]
    },
    {
      "name": "ProjectLoad",
      "description": "ProjectHeader plus the following load fields.",
      "fields": [
        [
          "needsRepair",
          "boolean",
          "Yes",
          "-",
          "Duplicate repair occurred in memory."
        ],
        [
          "repairIssues",
          "string[]",
          "Conditional",
          "Absent",
          "Included when needsRepair is true."
        ],
        [
          "migration",
          "Migration",
          "Conditional",
          "Absent",
          "Current compatibility normalization report."
        ]
      ]
    },
    {
      "name": "ItemPage",
      "description": "Pagination is over top-level items only.",
      "fields": [
        [
          "offset, limit, total",
          "integer",
          "Yes",
          "-",
          "Effective offset/page limit/top-level count."
        ],
        [
          "items",
          "ProjectItem[]",
          "Yes",
          "-",
          "Page with nested children and available cueId annotations."
        ]
      ]
    },
    {
      "name": "ProjectSettings",
      "description": "Sparse accepted settings; invalid/unknown keys are dropped. All writable fields optional. Read responses also contain derived outputTargetLevels. Defaults below are runtime fallbacks or explicitly identified client defaults.",
      "fields": [
        [
          "ltcOutput",
          "string or null",
          "No",
          "Empty",
          "Logical timecode output; empty/null disconnects it."
        ],
        [
          "outputTarget",
          "string",
          "No",
          "ebu-r128",
          "ebu-r128/streaming/radio/netflix/live."
        ],
        [
          "meterBallistics",
          "string",
          "No",
          "digital-ppm",
          "digital-ppm/ppm-i/ppm-ii/vu/instant/custom."
        ],
        [
          "meterBallisticsCustom",
          "object",
          "No",
          "1/300/300 ms",
          "attackMs 0..5000, releaseMs 0..10000, rmsWindowMs 1..10000, clamped."
        ],
        [
          "disableLimiter",
          "boolean",
          "No",
          "false",
          "Disable master limiter for this project."
        ],
        [
          "defaultTransitionMode",
          "string",
          "No",
          "Client import default",
          "crossfade/start-next."
        ],
        [
          "autoCueNextWithoutEndBehavior",
          "boolean",
          "No",
          "false",
          "Automatic arming when no end behavior is set."
        ],
        [
          "stopAllFadeMs",
          "number converted to integer",
          "No",
          "1000",
          "Clamped 0..60000."
        ],
        [
          "stopAllStopsPreview",
          "boolean",
          "No",
          "true",
          "Include pre-listen in Stop All."
        ],
        [
          "disableAutoVolumeAndTrim, disableSilenceWarning",
          "boolean",
          "No",
          "Client default false",
          "Import/operational UI settings."
        ],
        [
          "autoSave",
          "boolean",
          "No",
          "Client default",
          "Client save policy."
        ],
        [
          "indexDisplayStart",
          "number converted to integer",
          "No",
          "Client display default",
          "Clamped 0..1000000; API indices remain zero-based."
        ],
        [
          "defaultOutputDevice, previewDevice, ltcDevice",
          "string or null",
          "Compatibility",
          "-",
          "Accepted for compatible documents; normalized into bus/output configuration. Use buses and ltcOutput for new integrations."
        ],
        [
          "outputTargetLevels",
          "object",
          "Read only",
          "Computed",
          "Numeric blueBelow/greenMin/greenMax/yellowMin/yellowMax/redAbove/limiterCeilingDb/autoVolumeTargetDb; meterUnit and waveformColor strings."
        ]
      ]
    },
    {
      "name": "Bus",
      "description": "GET includes all runtime fields below. PATCH accepts name/color/order/width/gainDb/mute/pan/dsp/output/sends plus role acquisition. Creation accepts only the fields listed on POST /api/buses.",
      "fields": [
        [
          "id",
          "string",
          "Read only",
          "Generated",
          "Stable bus identity."
        ],
        [
          "name, color",
          "string",
          "No",
          "Unchanged",
          "Display name/color."
        ],
        [
          "order",
          "integer",
          "No",
          "Unchanged",
          "Display order."
        ],
        [
          "width",
          "integer",
          "No",
          "Unchanged",
          "Clamped 1..2."
        ],
        [
          "gainDb",
          "number (dB)",
          "No",
          "Unchanged",
          "Fader."
        ],
        [
          "mute",
          "boolean",
          "No",
          "Unchanged",
          "Post-processing mute."
        ],
        [
          "pan",
          "number",
          "No",
          "Unchanged",
          "Clamped -1..1. Mono: constant-power pan. Stereo: balance."
        ],
        [
          "dsp",
          "BusDsp",
          "No",
          "Unchanged",
          "Partial nested merge."
        ],
        [
          "master, preview",
          "boolean",
          "No",
          "Unchanged",
          "To acquire a role send true; false is refused. One role each, never both on one bus. Role holders need output.type=output."
        ],
        [
          "output",
          "BusOutput",
          "No",
          "Unchanged",
          "One destination. Role/target/cycle violations return 409."
        ],
        [
          "sends",
          "BusSend[]",
          "No",
          "Unchanged",
          "Replaces entire list; [] clears. Preview cannot send or be a destination. Combined output/send graph must be acyclic."
        ],
        [
          "pfl, monoCheck, bound",
          "boolean",
          "Read only",
          "-",
          "Live listen/mono state and whether output reaches hardware."
        ],
        [
          "outputDevices",
          "string[]",
          "Read only",
          "-",
          "Resolved device names."
        ],
        [
          "masters",
          "integer[2] or null",
          "Read only",
          "-",
          "Hardware-bound master pair; house 0/1, Preview highest pair."
        ],
        [
          "mixerId",
          "string",
          "Read only",
          "-",
          "Engine strip ID; may be empty during rebuild."
        ],
        [
          "itemUuids",
          "string[]",
          "Read only",
          "-",
          "Audio items whose bus resolves here, including inherited assignments."
        ]
      ]
    },
    {
      "name": "BusOutput",
      "description": "Routing types are explicitly serialized. The supported compatibility value master (and unrecognized types) resolves to the current Master-role bus. Prefer explicit types for new integrations.",
      "fields": [
        [
          "type",
          "string",
          "Yes",
          "master compatibility",
          "bus/output/none."
        ],
        [
          "target",
          "string",
          "No",
          "Empty",
          "Bus id for bus; logical output or present device name for output; ignored/cleared for none."
        ]
      ]
    },
    {
      "name": "BusSend",
      "description": "Auxiliary copy of a bus signal. No send is a separate hardware assignment.",
      "fields": [
        [
          "id",
          "string",
          "Yes",
          "-",
          "Destination bus id."
        ],
        [
          "levelDb",
          "number (dB)",
          "No",
          "0",
          "Clamped -120..12."
        ],
        [
          "tap",
          "string",
          "No",
          "post",
          "pre: after processing, before fader/mute; all other strings read as post."
        ]
      ]
    },
    {
      "name": "BusDsp",
      "description": "Complete stored bus DSP. Every field is emitted on reads; fields are optional in partial updates. Defaults describe a fresh bus. Omitted fields retain stored values, not defaults. EQ merges by slot (0..31); nested processor objects merge by field. Read it in a bus response; update it with the bus PATCH or live DSP POST methods.",
      "fields": [
        [
          "eqEnabled",
          "boolean",
          "No (partial update)",
          "true",
          "Enables the EQ band array only; does not bypass the separate HPF/LPF."
        ],
        [
          "dynEnabled",
          "boolean",
          "No (partial update)",
          "true",
          "Enables gate and compressor together; does not bypass stereo width."
        ],
        [
          "hpf.freq",
          "number (Hz)",
          "No (partial update)",
          "20",
          "Stored without a frequency clamp. Active above 20; <=0 uses 20 internally. Park at 20 to bypass."
        ],
        [
          "hpf.q",
          "number",
          "No (partial update)",
          "0.70710678",
          "Clamped to 0.1..40. Resonance of the separate high-pass biquad."
        ],
        [
          "lpf.freq",
          "number (Hz)",
          "No (partial update)",
          "20000",
          "Stored without a frequency clamp. Active when >0 and <20000; <=0 uses 20000 internally. Park at 20000 to bypass."
        ],
        [
          "lpf.q",
          "number",
          "No (partial update)",
          "0.70710678",
          "Clamped to 0.1..40. Resonance of the separate low-pass biquad."
        ],
        [
          "eq",
          "object[]",
          "No (partial update)",
          "4 enabled, flat bell bands",
          "Index-wise merge, maximum 32 slots (0..31). Initial (freq Hz, q): (100,0.7), (500,1), (2500,1), (10000,0.7). Missing trailing entries remain. [] does not clear bands."
        ],
        [
          "eq[].on",
          "boolean",
          "No (partial update)",
          "true",
          "False disables the slot; trailing disabled slots are removed. Interior slots keep their indices. Not a separate stable band ID."
        ],
        [
          "eq[].freq",
          "number (Hz)",
          "No (partial update)",
          "1000 for a newly appended slot",
          "Clamped to 10..24000. Existing slots retain their frequency when omitted. Engine filter design also clamps to its sample-rate limits."
        ],
        [
          "eq[].gain",
          "number (dB)",
          "No (partial update)",
          "0",
          "Clamped to -24..24. Used by bell/shelves; cuts and notch ignore gain. Flat bell/shelf is a no-op."
        ],
        [
          "eq[].q",
          "number",
          "No (partial update)",
          "1 for a newly appended slot",
          "Clamped to 0.1..40. Used by bell, lowCut, highCut and notch. Shelves use slope instead."
        ],
        [
          "eq[].type",
          "string",
          "No (partial update)",
          "bell",
          "Case-sensitive: bell, lowShelf, highShelf, lowCut, highCut, notch. Unknown string retains the existing type. lowCut = high-pass; highCut = low-pass."
        ],
        [
          "eq[].slope",
          "number",
          "No (partial update)",
          "1",
          "Clamped to 0.1..2. Shelf shape parameter, not dB/octave; 1 is the non-peaking steep shelf shape. Ignored by other types."
        ],
        [
          "eq[].shelf",
          "boolean",
          "No (partial update)",
          "Derived in responses",
          "Compatibility input applied AFTER type. false turns a shelf into bell; true promotes non-shelves only at slot 0 (lowShelf) or 3 (highShelf). Omit this field when editing type."
        ],
        [
          "gate.on",
          "boolean",
          "No (partial update)",
          "false",
          "Individual expander/gate switch; also requires dynEnabled, ratio >1 and range <0 to process."
        ],
        [
          "gate.threshold",
          "number (dB)",
          "No (partial update)",
          "-40",
          "Clamped to -80..0. Level below which downward expansion applies."
        ],
        [
          "gate.ratio",
          "number",
          "No (partial update)",
          "2",
          "Clamped to 1..20. 1 is a no-op. Higher ratios produce stronger gating."
        ],
        [
          "gate.range",
          "number (dB)",
          "No (partial update)",
          "-20",
          "Clamped to -80..0. Maximum attenuation, expressed as a negative value. 0 is a no-op."
        ],
        [
          "gate.attack",
          "number (ms)",
          "No (partial update)",
          "1",
          "Clamped to 0.1..100. Gate opening time."
        ],
        [
          "gate.hold",
          "number (ms)",
          "No (partial update)",
          "10",
          "Clamped to 0..1000. Hold time before closing."
        ],
        [
          "gate.release",
          "number (ms)",
          "No (partial update)",
          "100",
          "Clamped to 5..5000. Gate closing time."
        ],
        [
          "comp.on",
          "boolean",
          "No (partial update)",
          "false",
          "Individual compressor switch; also requires dynEnabled. Ratio 1 and makeup 0 is a no-op."
        ],
        [
          "comp.threshold",
          "number (dB)",
          "No (partial update)",
          "-18",
          "Clamped to -60..0. Compression threshold."
        ],
        [
          "comp.ratio",
          "number",
          "No (partial update)",
          "4",
          "Clamped to 1..60. Ratio 1 with nonzero makeup still processes."
        ],
        [
          "comp.makeup",
          "number (dB)",
          "No (partial update)",
          "0",
          "Clamped to -12..24. Output makeup gain; excluded from the reported gain-reduction value."
        ],
        [
          "comp.attack",
          "number (ms)",
          "No (partial update)",
          "10",
          "Clamped to 0.1..300. Time for gain reduction to increase."
        ],
        [
          "comp.knee",
          "number (dB)",
          "No (partial update)",
          "6",
          "Clamped to 0..24. Soft-knee width; 0 is a hard knee."
        ],
        [
          "comp.release",
          "number (ms)",
          "No (partial update)",
          "200",
          "Clamped to 5..5000. Time for gain to recover."
        ],
        [
          "width.width",
          "number",
          "No (partial update)",
          "1",
          "Clamped to 0..2: 0 mono, 1 unchanged image, 2 wider. Stereo buses only. This differs from top-level bus.width, the channel count."
        ],
        [
          "width.bassMonoHz",
          "number (Hz)",
          "No (partial update)",
          "20",
          "Clamped to 20..500. High-pass cutoff on the side signal to narrow low frequencies. Park at 20 to bypass bass-mono filtering."
        ],
        [
          "width.bassMonoQ",
          "number",
          "No (partial update)",
          "0.70710678",
          "Clamped to 0.1..4. Resonance of the side-signal high-pass."
        ]
      ],
      "example": {
        "eqEnabled": true,
        "dynEnabled": true,
        "hpf": {
          "freq": 20,
          "q": 0.70710678
        },
        "lpf": {
          "freq": 20000,
          "q": 0.70710678
        },
        "eq": [
          {
            "freq": 100,
            "gain": 0,
            "q": 0.7,
            "shelf": false,
            "slope": 1,
            "type": "bell",
            "on": true
          },
          {
            "freq": 500,
            "gain": 0,
            "q": 1,
            "shelf": false,
            "slope": 1,
            "type": "bell",
            "on": true
          },
          {
            "freq": 2500,
            "gain": 0,
            "q": 1,
            "shelf": false,
            "slope": 1,
            "type": "bell",
            "on": true
          },
          {
            "freq": 10000,
            "gain": 0,
            "q": 0.7,
            "shelf": false,
            "slope": 1,
            "type": "bell",
            "on": true
          }
        ],
        "gate": {
          "on": false,
          "threshold": -40,
          "ratio": 2,
          "range": -20,
          "attack": 1,
          "hold": 10,
          "release": 100
        },
        "comp": {
          "on": false,
          "threshold": -18,
          "ratio": 4,
          "makeup": 0,
          "attack": 10,
          "knee": 6,
          "release": 200
        },
        "width": {
          "width": 1,
          "bassMonoHz": 20,
          "bassMonoQ": 0.70710678
        }
      },
      "links": [
        [
          "Read a bus",
          "#http-get-api-buses-string"
        ],
        [
          "Patch bus DSP (processor examples)",
          "#http-patch-api-buses-string"
        ],
        [
          "Temporary DSP updates",
          "#http-post-api-buses-string-dsp"
        ]
      ]
    },
    {
      "name": "OutputMap",
      "description": "Maps portable output names to host hardware. The channels array maps bus lanes in order. Unmapped Main Out resolves to the default device; unmapped Preview Out is silent. Other unmapped names bind only if a matching device is present.",
      "fields": [
        [
          "version",
          "integer",
          "Yes",
          "1",
          "Output-map serialization version."
        ],
        [
          "outputs",
          "object[]",
          "Yes",
          "-",
          "Each: name string; channels array of {device:string, hwChannel:integer}. Hardware channels are zero-based; nonempty device required."
        ],
        [
          "builtin",
          "string[]",
          "GET only",
          "-",
          "Main Out and Preview Out."
        ],
        [
          "rewiredBuses",
          "integer",
          "PUT only",
          "-",
          "Number of bus routings updated."
        ]
      ]
    },
    {
      "name": "UserPrefs",
      "description": "Sparse per-person overrides. Invalid values ignored; top-level null removes a key. Field omission preserves stored state. Anonymous clients manage preferences locally.",
      "fields": [
        [
          "theme",
          "object",
          "No",
          "Unchanged",
          "mode light/dark/system; accentColor #RGB or #RRGGBB; nested merge."
        ],
        [
          "meterMode",
          "string",
          "No",
          "Unchanged",
          "dBFS/dBTP/RMS/LUFS."
        ],
        [
          "uiScrollToPlaying",
          "boolean",
          "No",
          "Unchanged",
          "Client scroll preference."
        ],
        [
          "locale",
          "string",
          "No",
          "Unchanged",
          "1..16 alphanumeric, dash or underscore characters."
        ],
        [
          "playbackKeys",
          "object",
          "No",
          "Unchanged",
          "Map of action names (1..64 chars) to null or {key:string (1..32), ctrlKey?:bool, shiftKey?:bool, altKey?:bool}; modifiers default false; replaces map."
        ]
      ]
    },
    {
      "name": "ServerConfig",
      "description": "All keys optional; null clears a file override. Read GET /api/server/config for actual boot values and ranges. Precedence: defaults < file < environment < CLI. Every registered field currently needs restart.",
      "fields": [
        [
          "port",
          "integer",
          "No",
          "4480",
          "1..65535."
        ],
        [
          "bind",
          "string",
          "No",
          "0.0.0.0",
          "Nonempty, at most 4096 bytes."
        ],
        [
          "meterHz",
          "integer",
          "No",
          "30",
          "1..120."
        ],
        [
          "maxUploadMb",
          "integer",
          "No",
          "256",
          "1..8192 MiB; upload handler limit."
        ],
        [
          "mixSampleRate",
          "integer",
          "No",
          "48000",
          "8000..192000 Hz."
        ],
        [
          "renderBlock",
          "integer",
          "No",
          "256",
          "32..8192 frames."
        ],
        [
          "ringBlocks",
          "integer",
          "No",
          "6",
          "2..512."
        ],
        [
          "masterChannels",
          "integer",
          "No",
          "32",
          "4..1024."
        ],
        [
          "maxBuses",
          "integer",
          "No",
          "64",
          "2..512."
        ],
        [
          "masterCeilingDb",
          "number",
          "No",
          "-0.3",
          "-24..0 dB."
        ],
        [
          "fsRoots",
          "string[]",
          "No",
          "[]",
          "Server-path allow-list. [] means unrestricted. Invalid list elements filtered."
        ],
        [
          "corsOrigin",
          "string",
          "No",
          "*",
          "Nonempty, at most 4096 bytes; REST origin header and WS Origin gate."
        ],
        [
          "verbose",
          "boolean",
          "No",
          "false",
          "Debug logging."
        ]
      ]
    },
    {
      "name": "ServerConfigRead",
      "description": "Current file values and effective values are deliberately distinct.",
      "fields": [
        [
          "path",
          "string",
          "Yes",
          "-",
          "Configuration file."
        ],
        [
          "schemaVersion",
          "integer",
          "Yes",
          "1",
          "Schema version."
        ],
        [
          "locked",
          "boolean",
          "Yes",
          "-",
          "Launch-time lock on writes."
        ],
        [
          "fields",
          "object[]",
          "Yes",
          "-",
          "key/flag/help/source/type/appliesAt strings; policy/overridden bools; stored/value JSON or null; numeric min/max only for numeric kinds. type=int/real/text/bool/pathList; source=default/file/env/cli."
        ]
      ]
    },
    {
      "name": "ServerConfigPatch",
      "description": "The returned stored object is the complete sparse file, not just the submitted patch.",
      "fields": [
        [
          "stored",
          "object",
          "Yes",
          "-",
          "Stored overrides and schema_version."
        ],
        [
          "dropped",
          "string[]",
          "Yes",
          "-",
          "Rejected key descriptions."
        ],
        [
          "restartRequired",
          "string[]",
          "Yes",
          "-",
          "Submitted known fields that need restart and are not launch-overridden."
        ],
        [
          "overriddenAtLaunch",
          "string[]",
          "Yes",
          "-",
          "Submitted fields whose boot source is environment or CLI."
        ]
      ]
    },
    {
      "name": "Migration",
      "description": "Compatibility notifications are still emitted when loading supported older document shapes; all fields are present.",
      "fields": [
        [
          "itemsToMain, busesFromDeviceOverride, sendsDropped, userPrefsMigrated",
          "integer",
          "Yes",
          "0",
          "Counts of normalized item/bus/send/profile entries."
        ],
        [
          "mainOutputMigrated, previewDeviceMigrated, ltcDeviceMigrated, rolesMigrated",
          "boolean",
          "Yes",
          "false",
          "Which compatibility conversions occurred."
        ]
      ]
    },
    {
      "name": "StateSummary",
      "description": "Polling snapshot for control surfaces. The example on the endpoint is an abbreviated idle state; this table describes the complete structure.",
      "fields": [
        [
          "project",
          "object",
          "Yes",
          "-",
          "name string, itemCount integer (top-level), hasOpenProject/audioLoading booleans."
        ],
        [
          "playing",
          "object[]",
          "Yes",
          "-",
          "itemUuid/cueId/name/color/transport strings; paused boolean; playheadSec/elapsedSec/durationSec/remainingSec numbers; optional index integer[] and triggerSeq integer."
        ],
        [
          "playing[].transport",
          "string",
          "Yes",
          "-",
          "playing/fading_in/fading_out/paused; stopped entries omitted."
        ],
        [
          "next",
          "object or null",
          "Yes",
          "-",
          "itemUuid/source required strings (source override/auto); optional name/color/type strings and index integer[]."
        ],
        [
          "selection",
          "object or null",
          "Yes",
          "-",
          "itemUuid string, onAir bool; optional name/color/type strings and index integer[]."
        ],
        [
          "ui",
          "object",
          "Yes",
          "-",
          "showMode boolean; locale string."
        ],
        [
          "master",
          "object",
          "Yes",
          "-",
          "gainDb number; limiterEnabled boolean."
        ],
        [
          "cart",
          "object[]",
          "Yes",
          "-",
          "slot integer, itemUuid string, playing boolean; optional name/color strings."
        ],
        [
          "buses",
          "object[]",
          "Yes",
          "-",
          "Bus subset: id/name/color strings, order/width integers, gainDb number, mute/pfl/bound/master/preview booleans, masters pair or null, output BusOutput, sends BusSend[]. monoCheck only on Preview."
        ],
        [
          "server",
          "object",
          "Yes",
          "-",
          "version string; meterBroadcastHz/masterChannels/previewMasterL/previewMasterR/maxUploadBytes integers."
        ],
        [
          "preview",
          "object",
          "Yes",
          "-",
          "active boolean; itemUuid string."
        ]
      ]
    },
    {
      "name": "Cue",
      "description": "Engine cue response. Fields from engine stats are conditional on the cue being present in the engine.",
      "fields": [
        [
          "id, display_name, file_path, artist, title",
          "string",
          "Yes",
          "-",
          "Cue identity, labels and server path."
        ],
        [
          "duration_sec, gain_db",
          "number",
          "Yes",
          "-",
          "Duration in seconds and gain in dB."
        ],
        [
          "fade_in_ms, fade_out_ms",
          "integer",
          "Yes",
          "-",
          "Fade times."
        ],
        [
          "ltc",
          "object",
          "Yes",
          "-",
          "enabled bool, fps integer (0..4), offset_ns integer, start_timecode string."
        ],
        [
          "transport",
          "integer",
          "Conditional",
          "Absent",
          "Transport enum: 0 stopped, 1 playing, 2 fading in, 3 fading out, 4 paused."
        ],
        [
          "playhead_seconds",
          "number",
          "Conditional",
          "Absent",
          "Absolute file position."
        ],
        [
          "source_channels",
          "integer",
          "Conditional",
          "Absent",
          "Decoded channel count."
        ],
        [
          "file_loaded",
          "boolean",
          "Conditional",
          "Absent",
          "Engine file load state."
        ]
      ]
    },
    {
      "name": "Device",
      "description": "Device enumeration objects.",
      "fields": [
        [
          "id, display_name",
          "string",
          "Yes",
          "-",
          "Engine device identity and readable name."
        ],
        [
          "channel_count, sample_rate",
          "integer",
          "Yes",
          "-",
          "Reported output channels and sample rate in Hz."
        ],
        [
          "is_default",
          "boolean",
          "Yes",
          "-",
          "System default output."
        ]
      ]
    },
    {
      "name": "ClientSession",
      "description": "One established WebSocket connection.",
      "fields": [
        [
          "id",
          "integer",
          "Yes",
          "-",
          "Monotonic identifier within the server process."
        ],
        [
          "remoteIp",
          "string",
          "Yes",
          "-",
          "Peer address."
        ],
        [
          "user, userId",
          "string or null",
          "Yes",
          "-",
          "Credential name/id or null for anonymous."
        ],
        [
          "isAdmin",
          "boolean",
          "Yes",
          "-",
          "Handshake role."
        ],
        [
          "kind",
          "string",
          "Yes",
          "-",
          "anonymous/user/token."
        ],
        [
          "locale",
          "string",
          "Yes",
          "-",
          "Effective locale."
        ],
        [
          "localeIsOwn",
          "boolean",
          "Yes",
          "-",
          "Connection has explicitly chosen a locale."
        ],
        [
          "meterHz",
          "integer",
          "Yes",
          "-",
          "Effective delivery rate."
        ],
        [
          "meterHzIsOwn",
          "boolean",
          "Yes",
          "-",
          "Connection has requested a rate."
        ],
        [
          "connectedSeconds",
          "integer",
          "Yes",
          "-",
          "Connection age."
        ]
      ]
    },
    {
      "name": "FileListing",
      "description": "Root entries and directory entries share name/full_path/kind.",
      "fields": [
        [
          "path, parent",
          "string",
          "Yes",
          "-",
          "Server paths; empty path represents computer roots."
        ],
        [
          "is_root",
          "boolean",
          "Yes",
          "-",
          "True for computer-root listing."
        ],
        [
          "entries",
          "object[]",
          "Yes",
          "-",
          "name/full_path/kind strings; kind home/drive/dir/file; size integer bytes only for files."
        ]
      ]
    },
    {
      "name": "Metadata",
      "description": "Tag and audio properties; valid=false can accompany empty/default values.",
      "fields": [
        [
          "valid",
          "boolean",
          "Yes",
          "-",
          "Whether metadata was read."
        ],
        [
          "artist, title, album, genre",
          "string",
          "Yes",
          "-",
          "Tags."
        ],
        [
          "year, track_number, duration_ms, sample_rate, channels, bitrate_kbps",
          "integer",
          "Yes",
          "-",
          "Tag/audio numbers; units encoded in names."
        ]
      ]
    },
    {
      "name": "Waveform",
      "description": "Linear amplitude envelopes, one channel object per decoded source channel. Example arrays are intentionally short.",
      "fields": [
        [
          "cue_id",
          "string",
          "Cue route only",
          "Absent",
          "Engine cue identity."
        ],
        [
          "bucket_count, duration_ms, sample_rate, source_channels",
          "integer",
          "Yes",
          "-",
          "Actual bucket count, duration, Hz and channel count."
        ],
        [
          "channels",
          "object[]",
          "Yes",
          "-",
          "Each has peak:number[] and rms:number[] in source order; values are linear amplitude, not dB."
        ]
      ]
    },
    {
      "name": "EngineStats",
      "description": "Diagnostic numbers describe this running engine, not a fixed latency promise.",
      "fields": [
        [
          "queuedFrames, ringCapacityFrames, devicePeriodFrames, devicePeriods, blocksRendered, underruns, topologyRebuilds, discontinuities, deviceCount",
          "integer",
          "Yes",
          "-",
          "Queue sizes, actual hardware period information and counters."
        ],
        [
          "queuedMs, deviceMs, renderBlockUsMax, renderBlockUsAvg, blockBudgetUs, mutexWaitUsMax, worstSeam",
          "number",
          "Yes",
          "-",
          "Time quantities use the named unit; worstSeam is the seam diagnostic."
        ],
        [
          "devices",
          "object[]",
          "Yes",
          "-",
          "name string; isClock bool; ppm/fillPercent/ringFillPercent numbers. ppm is drift correction; fillPercent is resampler fill and ringFillPercent device queue fill."
        ]
      ]
    },
    {
      "name": "MeterSample",
      "description": "All meter sample keys are emitted. dB fields use -120 as the silence floor. peak/rms are dBFS; true_peak uses dBTP. kw_ms values are linear K-weighted mean-square energy, not dB.",
      "fields": [
        [
          "peak_db, rms_db",
          "number",
          "Yes",
          "-",
          "Ballistically released sample peak and windowed RMS."
        ],
        [
          "peak_max_db",
          "number",
          "Yes",
          "-",
          "Raw sample maximum since the server consumed the previous meter interval."
        ],
        [
          "true_peak_db, true_peak_max_db",
          "number",
          "Yes",
          "-",
          "True-peak equivalents; analysis may be gated off if no active meter mode needs it."
        ],
        [
          "kw_ms, kw_ms_s",
          "number",
          "Yes",
          "-",
          "K-weighted energy, approximately 400 ms and 3 s windows. Loudness for summed channels: -0.691 + 10*log10(sum energy); guard zero."
        ]
      ]
    },
    {
      "name": "PlaybackSnapshot",
      "description": "Sent once per connection on the next broadcast tick; no project document is included.",
      "fields": [
        [
          "type",
          "string",
          "Yes",
          "playback_snapshot",
          "Frame discriminator."
        ],
        [
          "cues",
          "object[]",
          "Yes",
          "-",
          "Non-stopped project cues: cue_id string, transport integer, playhead_seconds number; optional item_uuid string."
        ],
        [
          "next_item_uuid, selected_item_uuid",
          "string",
          "Yes",
          "-",
          "Shared session selection/arm; empty means none."
        ],
        [
          "master_gain_db",
          "number",
          "Yes",
          "-",
          "Live master gain."
        ],
        [
          "output_channel_gains",
          "object[]",
          "Yes",
          "-",
          "Only nonzero trims; channel integer, db number. Missing channel means 0 dB."
        ],
        [
          "show_mode",
          "boolean",
          "Yes",
          "-",
          "Shared show-mode signal."
        ],
        [
          "locale",
          "string",
          "Yes",
          "-",
          "Installation fallback at connection time."
        ],
        [
          "preview",
          "object",
          "Yes",
          "-",
          "item_uuid/cue_id strings; empty when inactive."
        ],
        [
          "master_bus",
          "object",
          "Yes",
          "-",
          "channels/preview_l/preview_r integers. Preview pair depends on configured master width."
        ]
      ]
    },
    {
      "name": "Meters",
      "description": "Periodic frame; no subscription is needed. Items are non-stopped engine/project and preview cues; stopped items disappear. Omitted master channels are silent. Map buses to mixer meters by Bus.mixerId; map cue_id to project cueId.",
      "fields": [
        [
          "type",
          "string",
          "Yes",
          "meters",
          "Frame discriminator."
        ],
        [
          "items",
          "object[]",
          "Yes",
          "-",
          "cue_id string; transport integer; playhead_seconds number; sources MeterSample[]."
        ],
        [
          "mixer_channels",
          "object[]",
          "Yes",
          "-",
          "mixer_id string; MeterSample aggregate fields; lanes MeterSample[] in lane order; gate_gr_db/comp_gr_db/dyn_in_db/dyn_out_db/correlation numbers."
        ],
        [
          "mixer_channels[].gate_gr_db, comp_gr_db",
          "number (dB)",
          "Yes",
          "-",
          "Gain reduction in dB, non-positive (0 means no reduction)."
        ],
        [
          "mixer_channels[].dyn_in_db, dyn_out_db",
          "number (dB)",
          "Yes",
          "-",
          "Dynamics input/output level."
        ],
        [
          "mixer_channels[].correlation",
          "number",
          "Yes",
          "-",
          "Stereo correlation."
        ],
        [
          "master_channels",
          "object[]",
          "Yes",
          "-",
          "index integer, MeterSample fields, gain_reduction_db number (non-positive). Only peak > -119 dB, max peak > -119 dB or reduction < -0.05 dB are emitted."
        ]
      ]
    }
  ],
  "websocket": {
    "commands": [
      {
        "type": "play",
        "purpose": "Trigger playback",
        "fields": [
          [
            "item_uuid",
            "string",
            "One of",
            "-",
            "Project UUID; takes precedence over cue_id."
          ],
          [
            "cue_id",
            "string",
            "One of",
            "-",
            "Engine cue id."
          ]
        ],
        "example": {
          "type": "play",
          "item_uuid": "opening"
        },
        "reply": "No direct success reply; observe state/events.",
        "notes": "item_uuid uses trigger_item (including group/toggle behavior). cue_id mapped to an item uses play_item; otherwise engine.play. Invalid targets can be silent no-ops."
      },
      {
        "type": "stop",
        "purpose": "Stop playback",
        "fields": [
          [
            "item_uuid",
            "string",
            "One of",
            "-",
            "Project UUID; takes precedence over cue_id."
          ],
          [
            "cue_id",
            "string",
            "One of",
            "-",
            "Engine cue id."
          ],
          [
            "fade_ms",
            "number (ms)",
            "No",
            "Cue manual-stop fade",
            "Truncated/clamped to at least 0."
          ]
        ],
        "example": {
          "type": "stop",
          "item_uuid": "opening",
          "fade_ms": 250
        },
        "reply": "No direct success reply; observe state/events.",
        "notes": ""
      },
      {
        "type": "pause",
        "purpose": "Pause a loaded cue",
        "fields": [
          [
            "item_uuid",
            "string",
            "One of",
            "-",
            "Project UUID; takes precedence over cue_id."
          ],
          [
            "cue_id",
            "string",
            "One of",
            "-",
            "Engine cue id."
          ]
        ],
        "example": {
          "type": "pause",
          "item_uuid": "opening"
        },
        "reply": "No direct success reply; observe state/events.",
        "notes": "error if target is unresolved or cue not loaded; applies to audio cues, not a group operation."
      },
      {
        "type": "resume",
        "purpose": "Resume a loaded cue",
        "fields": [
          [
            "item_uuid",
            "string",
            "One of",
            "-",
            "Project UUID; takes precedence over cue_id."
          ],
          [
            "cue_id",
            "string",
            "One of",
            "-",
            "Engine cue id."
          ]
        ],
        "example": {
          "type": "resume",
          "item_uuid": "opening"
        },
        "reply": "No direct success reply; observe state/events.",
        "notes": "error if target is unresolved or cue not loaded; applies to audio cues, not a group operation."
      },
      {
        "type": "seek",
        "purpose": "Seek a loaded cue",
        "fields": [
          [
            "item_uuid",
            "string",
            "One of",
            "-",
            "Project UUID; takes precedence over cue_id."
          ],
          [
            "cue_id",
            "string",
            "One of",
            "-",
            "Engine cue id."
          ],
          [
            "seconds",
            "number (s)",
            "No",
            "0",
            "Absolute file position."
          ]
        ],
        "example": {
          "type": "seek",
          "item_uuid": "opening",
          "seconds": 12.5
        },
        "reply": "No direct success reply; observe state/events.",
        "notes": "error for an unresolved or unloaded cue."
      },
      {
        "type": "stop_all",
        "purpose": "Stop show playback",
        "fields": [
          [
            "fade_ms",
            "integer (ms)",
            "No",
            "Project stopAllFadeMs, else 1000",
            "Common fade; state clamps negatives to 0."
          ]
        ],
        "example": {
          "type": "stop_all",
          "fade_ms": 0
        },
        "reply": "No direct success reply; observe state/events.",
        "notes": "Same preview inclusion setting as REST; cancels pending delayed advances. PFL is separate."
      },
      {
        "type": "go",
        "purpose": "Trigger armed or derived next cue",
        "fields": [],
        "example": {
          "type": "go"
        },
        "reply": "No direct success reply; observe state/events.",
        "notes": "error if no next target can be derived."
      },
      {
        "type": "gain",
        "purpose": "Set engine cue gain",
        "fields": [
          [
            "item_uuid",
            "string",
            "One of",
            "-",
            "Project UUID; takes precedence over cue_id."
          ],
          [
            "cue_id",
            "string",
            "One of",
            "-",
            "Engine cue id."
          ],
          [
            "db",
            "number (dB)",
            "No",
            "0",
            "Gain."
          ]
        ],
        "example": {
          "type": "gain",
          "cue_id": "cue-id",
          "db": -6
        },
        "reply": "No direct success reply; observe state/events.",
        "notes": "Unresolved target is silently ignored."
      },
      {
        "type": "fade",
        "purpose": "Set engine cue fades",
        "fields": [
          [
            "item_uuid",
            "string",
            "One of",
            "-",
            "Project UUID; takes precedence over cue_id."
          ],
          [
            "cue_id",
            "string",
            "One of",
            "-",
            "Engine cue id."
          ],
          [
            "in_ms",
            "integer (ms)",
            "No",
            "0",
            "Fade-in."
          ],
          [
            "out_ms",
            "integer (ms)",
            "No",
            "0",
            "Fade-out."
          ]
        ],
        "example": {
          "type": "fade",
          "cue_id": "cue-id",
          "in_ms": 250,
          "out_ms": 1000
        },
        "reply": "No direct success reply; observe state/events.",
        "notes": ""
      },
      {
        "type": "set_next_item",
        "purpose": "Arm or clear the next-item override",
        "fields": [
          [
            "item_uuid",
            "string",
            "No",
            "Empty",
            "Empty/missing/non-string clears."
          ]
        ],
        "example": {
          "type": "set_next_item",
          "item_uuid": "opening"
        },
        "reply": "No direct success reply; observe state/events.",
        "notes": "Emits doc_patch next_item_set. UUID is not validated by this handler."
      },
      {
        "type": "set_selection",
        "purpose": "Set shared selection",
        "fields": [
          [
            "item_uuid",
            "string",
            "No",
            "Empty",
            "Empty/missing/non-string clears."
          ]
        ],
        "example": {
          "type": "set_selection",
          "item_uuid": "opening"
        },
        "reply": "No direct success reply; observe state/events.",
        "notes": "Emits selection_changed."
      },
      {
        "type": "select_step",
        "purpose": "Step shared playlist selection",
        "fields": [
          [
            "delta",
            "integer",
            "No",
            "0",
            "Nonzero step; 0 does nothing."
          ]
        ],
        "example": {
          "type": "select_step",
          "delta": 1
        },
        "reply": "No direct success reply; observe state/events.",
        "notes": "Uses current selection or active playback anchor; emits selection_changed when state setter is called."
      },
      {
        "type": "set_show_mode",
        "purpose": "Set or toggle shared show mode",
        "fields": [
          [
            "enabled",
            "boolean",
            "No",
            "Toggle",
            "Non-boolean also toggles."
          ]
        ],
        "example": {
          "type": "set_show_mode",
          "enabled": true
        },
        "reply": "No direct success reply; observe state/events.",
        "notes": "Emits show_mode_changed."
      },
      {
        "type": "set_locale",
        "purpose": "Set this connection's locale",
        "fields": [
          [
            "locale",
            "string",
            "Yes",
            "-",
            "Empty does not clear an existing choice."
          ]
        ],
        "example": {
          "type": "set_locale",
          "locale": "en"
        },
        "reply": "doc_patch locale_changed sent only to the requesting socket.",
        "notes": "No locale-code validation. A valid string is needed to enter this branch; otherwise no reply."
      },
      {
        "type": "set_meter_hz",
        "purpose": "Set this connection's delivery rate",
        "fields": [
          [
            "hz",
            "number",
            "Yes",
            "-",
            "<=0 resets to server tick rate; positive fractions truncate; clamped to server rate."
          ]
        ],
        "example": {
          "type": "set_meter_hz",
          "hz": 10
        },
        "reply": "doc_patch meter_hz_changed with effective integer hz.",
        "notes": "Does not change measurement cadence or slow other clients. 0 cannot disable meters."
      },
      {
        "type": "set_analyser",
        "purpose": "Subscribe this connection to one bus spectrum",
        "fields": [
          [
            "busId",
            "string",
            "No",
            "Empty",
            "Empty clears subscription."
          ]
        ],
        "example": {
          "type": "set_analyser",
          "busId": "music"
        },
        "reply": "analyser_subscribed with busId.",
        "notes": "Use set_analyser with a busId to receive analyser frames for the bus; send an empty busId to unsubscribe. One selected bus per connection, at most four distinct bus taps engine-wide. An analyser_subscribed reply is not proof of valid bus/capacity or eventual frames. No frame is sent for an unavailable bus.",
        "links": [
          [
            "Analyser frames",
            "#event-analyser"
          ]
        ]
      },
      {
        "type": "bus_gain",
        "purpose": "Set and store bus gain",
        "fields": [
          [
            "busId",
            "string",
            "Yes",
            "-",
            "Nonempty bus id."
          ],
          [
            "gainDb",
            "number (dB)",
            "No",
            "0",
            "Fader."
          ]
        ],
        "example": {
          "type": "bus_gain",
          "busId": "music",
          "gainDb": -6
        },
        "reply": "No direct success reply; observe state/events.",
        "notes": "Emits buses_patched to all clients; error for missing/unknown bus."
      },
      {
        "type": "bus_mute",
        "purpose": "Set or toggle bus mute",
        "fields": [
          [
            "busId",
            "string",
            "Yes",
            "-",
            "Nonempty bus id."
          ],
          [
            "mute",
            "boolean",
            "No",
            "Toggle",
            "Missing/non-boolean toggles."
          ]
        ],
        "example": {
          "type": "bus_mute",
          "busId": "music",
          "mute": true
        },
        "reply": "No direct success reply; observe state/events.",
        "notes": "Stores in-memory bus state and emits buses_patched."
      },
      {
        "type": "bus_pfl",
        "purpose": "Set or toggle a PFL tap",
        "fields": [
          [
            "busId",
            "string",
            "Yes",
            "-",
            "Nonempty bus id."
          ],
          [
            "pfl",
            "boolean",
            "No",
            "Toggle",
            "Missing/non-boolean toggles."
          ]
        ],
        "example": {
          "type": "bus_pfl",
          "busId": "music",
          "pfl": true
        },
        "reply": "No direct success reply; observe state/events.",
        "notes": "Emits bus_pfl_changed. Preview cannot be PFL'd. Session-only."
      },
      {
        "type": "ping",
        "purpose": "Application-level liveness probe",
        "fields": [],
        "example": {
          "type": "ping"
        },
        "reply": "{\"type\":\"pong\"}",
        "notes": "No server-initiated JSON heartbeat or mandated ping interval."
      }
    ],
    "events": [
      {
        "type": "playback_snapshot",
        "purpose": "Initial playback/session snapshot",
        "fields": [],
        "example": {
          "type": "playback_snapshot",
          "cues": [],
          "next_item_uuid": "",
          "master_gain_db": 0,
          "output_channel_gains": [],
          "selected_item_uuid": "",
          "show_mode": false,
          "locale": "en",
          "preview": {
            "item_uuid": "",
            "cue_id": ""
          },
          "master_bus": {
            "channels": 32,
            "preview_l": 30,
            "preview_r": 31
          }
        },
        "notes": " Fetch GET /api/buses on initial connection, after reconnect/project changes and when bus topology changes to obtain mixerId mappings. playback_snapshot carries compact bus state without dsp, and there is no initial full-DSP WebSocket snapshot or replay.",
        "schema": "PlaybackSnapshot"
      },
      {
        "type": "meters",
        "purpose": "Periodic meter/playhead update",
        "fields": [],
        "example": {
          "type": "meters",
          "items": [],
          "mixer_channels": [],
          "master_channels": []
        },
        "notes": "Empty arrays are valid idle state. See Meters and MeterSample for populated objects. In meters.mixer_channels, match mixer_id to the Bus.mixerId obtained over HTTP. gate_gr_db and comp_gr_db are non-positive dB values (0 = no reduction), reporting deepest reduction since the last meter tick. dyn_in_db/dyn_out_db are pre-fader dynamics input/output peak levels in dBFS. correlation reports stereo agreement (+1 aligned, 0 uncorrelated, negative cancellation). Meters do not carry the DSP parameter settings.",
        "schema": "Meters",
        "links": [
          [
            "Read bus mixerId mappings",
            "#http-get-api-buses"
          ]
        ]
      },
      {
        "type": "cue_state",
        "purpose": "Project cue transport transition",
        "fields": [
          [
            "cue_id",
            "string",
            "Yes",
            "-",
            "Engine cue."
          ],
          [
            "item_uuid",
            "string",
            "Conditional",
            "Absent",
            "Project UUID when mapped."
          ],
          [
            "transport",
            "integer",
            "Yes",
            "-",
            "0 stopped, 1 playing, 2 fading in, 3 fading out, 4 paused."
          ],
          [
            "playhead_seconds",
            "number",
            "Yes",
            "-",
            "File position."
          ]
        ],
        "example": {
          "type": "cue_state",
          "cue_id": "cue-id",
          "item_uuid": "opening",
          "transport": 1,
          "playhead_seconds": 0
        },
        "notes": "Broadcast when the meter loop observes a transition, independent of per-client meter thinning. Fast transitions between ticks can be missed. Preview is not part of this cue-state scan.",
        "schema": null
      },
      {
        "type": "analyser",
        "purpose": "Subscribed bus spectrum",
        "fields": [
          [
            "busId",
            "string",
            "Yes",
            "-",
            "Requested bus id."
          ],
          [
            "fLo, fHi",
            "number (Hz)",
            "Yes",
            "20, 20000",
            "Log-frequency endpoints."
          ],
          [
            "pre, post",
            "number[]",
            "Yes",
            "-",
            "96 spectrum bins each, dB, rounded to 0.1 dB."
          ]
        ],
        "example": {
          "type": "analyser",
          "busId": "music",
          "fLo": 20,
          "fHi": 20000,
          "pre": [
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120
          ],
          "post": [
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120,
            -120
          ]
        },
        "notes": "Delivered only to subscribers, at their meter delivery rate; FFT size 4096.",
        "schema": null
      },
      {
        "type": "analyser_subscribed",
        "purpose": "Acknowledgment of set_analyser",
        "fields": [
          [
            "busId",
            "string",
            "Yes",
            "-",
            "Requested id; empty unsubscribes."
          ]
        ],
        "example": {
          "type": "analyser_subscribed",
          "busId": "music"
        },
        "notes": "Direct reply; does not assert the bus exists or that a tap was allocated.",
        "schema": null
      },
      {
        "type": "pong",
        "purpose": "Response to JSON ping",
        "fields": [],
        "example": {
          "type": "pong"
        },
        "notes": "Direct reply; has no timestamp or request id.",
        "schema": null
      },
      {
        "type": "error",
        "purpose": "Command or parse error",
        "fields": [
          [
            "message",
            "string",
            "Yes",
            "-",
            "Diagnostic text; not a stable machine error code."
          ]
        ],
        "example": {
          "type": "error",
          "message": "unknown type"
        },
        "notes": "Direct reply when the handler produces an error; not every invalid command is acknowledged. A malformed top-level shape/type can escape to logging with no reply.",
        "schema": null
      },
      {
        "type": "doc_patch",
        "purpose": "State notification envelope",
        "fields": [
          [
            "op",
            "string",
            "Yes",
            "-",
            "Operation discriminator in the catalog below."
          ],
          [
            "(other fields)",
            "Operation-specific",
            "See op",
            "-",
            "Payload is on the top-level object."
          ]
        ],
        "example": {
          "type": "doc_patch",
          "op": "project_changed"
        },
        "notes": "This is a named-event envelope, not JSON Patch or a revisioned delta stream.",
        "schema": null
      }
    ],
    "patches": [
      {
        "op": "project_changed",
        "purpose": "Active project loaded, closed or document replaced",
        "fields": [],
        "example": {
          "type": "doc_patch",
          "op": "project_changed"
        },
        "notes": "Re-fetch header/items/buses; no document is included. Ordinary save does not emit this."
      },
      {
        "op": "project_migrated",
        "purpose": "Supported document compatibility normalization",
        "fields": [
          [
            "(payload)",
            "Migration fields",
            "Yes",
            "-",
            "See Migration; fields are top-level."
          ]
        ],
        "example": {
          "type": "doc_patch",
          "op": "project_migrated",
          "itemsToMain": 1,
          "busesFromDeviceOverride": 0,
          "mainOutputMigrated": false,
          "previewDeviceMigrated": false,
          "ltcDeviceMigrated": false,
          "sendsDropped": 0,
          "userPrefsMigrated": 0,
          "rolesMigrated": false
        },
        "notes": ""
      },
      {
        "op": "item_added",
        "purpose": "POST project/items completed",
        "fields": [
          [
            "uuid, parentUuid, cueId",
            "string",
            "Yes",
            "-",
            "Submitted UUID/parent and returned cue id."
          ],
          [
            "cartOnly",
            "boolean",
            "Yes",
            "-",
            "Requested destination."
          ],
          [
            "item",
            "ProjectItem",
            "Yes",
            "-",
            "Submitted item."
          ]
        ],
        "example": {
          "type": "doc_patch",
          "op": "item_added",
          "uuid": "opening",
          "parentUuid": "",
          "cartOnly": false,
          "item": {
            "uuid": "opening",
            "type": "audio",
            "mediaPath": "media/opening.wav"
          },
          "cueId": "cue-id"
        },
        "notes": "Can be emitted even if state did not insert the item. Fetch current state to confirm."
      },
      {
        "op": "item_updated",
        "purpose": "PATCH project item completed",
        "fields": [
          [
            "uuid",
            "string",
            "Yes",
            "-",
            "Project identity."
          ],
          [
            "patch",
            "object",
            "Yes",
            "-",
            "Submitted item patch."
          ]
        ],
        "example": {
          "type": "doc_patch",
          "op": "item_updated",
          "uuid": "opening",
          "patch": {
            "volume": 0.5
          }
        },
        "notes": ""
      },
      {
        "op": "item_removed",
        "purpose": "Project item removed",
        "fields": [
          [
            "uuid",
            "string",
            "Yes",
            "-",
            "Removed identity."
          ]
        ],
        "example": {
          "type": "doc_patch",
          "op": "item_removed",
          "uuid": "opening"
        },
        "notes": ""
      },
      {
        "op": "items_reordered",
        "purpose": "Reorder request completed",
        "fields": [
          [
            "parentUuid",
            "string",
            "Yes",
            "-",
            "Empty for root."
          ],
          [
            "uuids",
            "string[]",
            "Yes",
            "-",
            "Submitted string IDs."
          ]
        ],
        "example": {
          "type": "doc_patch",
          "op": "items_reordered",
          "parentUuid": "",
          "uuids": [
            "opening",
            "sting"
          ]
        },
        "notes": ""
      },
      {
        "op": "cart_slot_set",
        "purpose": "Cart binding request completed",
        "fields": [
          [
            "slot",
            "integer",
            "Yes",
            "-",
            "Zero-based slot."
          ],
          [
            "itemUuid",
            "string",
            "Yes",
            "-",
            "Submitted UUID."
          ]
        ],
        "example": {
          "type": "doc_patch",
          "op": "cart_slot_set",
          "slot": 0,
          "itemUuid": "opening"
        },
        "notes": ""
      },
      {
        "op": "cart_slot_cleared",
        "purpose": "Cart binding removed",
        "fields": [
          [
            "slot",
            "integer",
            "Yes",
            "-",
            "Zero-based slot."
          ]
        ],
        "example": {
          "type": "doc_patch",
          "op": "cart_slot_cleared",
          "slot": 0
        },
        "notes": ""
      },
      {
        "op": "settings_patched",
        "purpose": "Project settings updated",
        "fields": [
          [
            "settings",
            "ProjectSettings",
            "Yes",
            "-",
            "Entire settings object including derived levels."
          ]
        ],
        "example": {
          "type": "doc_patch",
          "op": "settings_patched",
          "settings": {
            "stopAllFadeMs": 1000
          }
        },
        "notes": "Example abbreviated."
      },
      {
        "op": "buses_patched",
        "purpose": "Bus settings changed, including DSP, gain, mute and routing",
        "fields": [
          [
            "buses",
            "Bus[] (stored definitions)",
            "Yes",
            "-",
            "Full in-memory bus document array; no live fields."
          ]
        ],
        "example": {
          "type": "doc_patch",
          "op": "buses_patched",
          "buses": []
        },
        "notes": "Re-fetch /api/buses for mixerId, bound, masters, PFL and item attribution; empty array here illustrates shape only. After an HTTP DSP PATCH, sockets receive doc_patch / buses_patched. Its buses array contains the complete stored bus collection, including bus.dsp, rather than a delta of the edited fields. It omits runtime mixerId, bound and pfl fields.",
        "relatedSchemas": [
          "BusDsp"
        ],
        "links": [
          [
            "Read buses and runtime mappings",
            "#http-get-api-buses"
          ],
          [
            "Update bus settings",
            "#http-patch-api-buses-string"
          ]
        ]
      },
      {
        "op": "outputs_changed",
        "purpose": "Logical output map changed or a device name materialized",
        "fields": [
          [
            "version",
            "integer",
            "Yes",
            "1",
            "Map version."
          ],
          [
            "outputs",
            "OutputMapping[]",
            "Yes",
            "-",
            "Complete stored mappings."
          ],
          [
            "rewiredBuses",
            "integer",
            "Yes",
            "-",
            "Count; materialization broadcast uses 0."
          ]
        ],
        "example": {
          "type": "doc_patch",
          "op": "outputs_changed",
          "version": 1,
          "outputs": [],
          "rewiredBuses": 0
        },
        "notes": ""
      },
      {
        "op": "bus_pfl_changed",
        "purpose": "A PFL tap was set",
        "fields": [
          [
            "id",
            "string",
            "Yes",
            "-",
            "Bus id (not busId)."
          ],
          [
            "pfl",
            "boolean",
            "Yes",
            "-",
            "New tap state."
          ]
        ],
        "example": {
          "type": "doc_patch",
          "op": "bus_pfl_changed",
          "id": "music",
          "pfl": true
        },
        "notes": ""
      },
      {
        "op": "bus_pfl_cleared",
        "purpose": "One or more PFL taps were cleared",
        "fields": [],
        "example": {
          "type": "doc_patch",
          "op": "bus_pfl_cleared"
        },
        "notes": ""
      },
      {
        "op": "monitor_mono_changed",
        "purpose": "Preview mono audition was set",
        "fields": [
          [
            "mono",
            "boolean",
            "Yes",
            "-",
            "Mono state."
          ]
        ],
        "example": {
          "type": "doc_patch",
          "op": "monitor_mono_changed",
          "mono": true
        },
        "notes": "The op name is the same for /api/preview/mono and /api/monitor/mono."
      },
      {
        "op": "preview_started",
        "purpose": "Pre-listen started",
        "fields": [
          [
            "itemUuid, cueId",
            "string",
            "Yes",
            "-",
            "Project UUID and transient preview cue."
          ]
        ],
        "example": {
          "type": "doc_patch",
          "op": "preview_started",
          "itemUuid": "opening",
          "cueId": "preview-cue"
        },
        "notes": ""
      },
      {
        "op": "preview_stopped",
        "purpose": "Pre-listen stopped or reached its end",
        "fields": [],
        "example": {
          "type": "doc_patch",
          "op": "preview_stopped"
        },
        "notes": "Also emitted when stopping through other preview-aware paths; no IDs in the event."
      },
      {
        "op": "master_gain_changed",
        "purpose": "Live master gain changed through REST",
        "fields": [
          [
            "db",
            "number (dB)",
            "Yes",
            "-",
            "Effective master gain."
          ]
        ],
        "example": {
          "type": "doc_patch",
          "op": "master_gain_changed",
          "db": -6
        },
        "notes": ""
      },
      {
        "op": "limiter_changed",
        "purpose": "Live limiter changed through REST",
        "fields": [
          [
            "enabled",
            "boolean",
            "Yes",
            "-",
            "Limiter state."
          ]
        ],
        "example": {
          "type": "doc_patch",
          "op": "limiter_changed",
          "enabled": true
        },
        "notes": ""
      },
      {
        "op": "output_channel_gain_changed",
        "purpose": "Output trim changed through REST",
        "fields": [
          [
            "channel",
            "integer",
            "Yes",
            "-",
            "Zero-based master channel."
          ],
          [
            "db",
            "number (dB)",
            "Yes",
            "-",
            "Effective trim."
          ]
        ],
        "example": {
          "type": "doc_patch",
          "op": "output_channel_gain_changed",
          "channel": 0,
          "db": -3
        },
        "notes": ""
      },
      {
        "op": "next_item_set",
        "purpose": "Armed next-item override changed",
        "fields": [
          [
            "itemUuid",
            "string",
            "Yes",
            "-",
            "Empty clears."
          ]
        ],
        "example": {
          "type": "doc_patch",
          "op": "next_item_set",
          "itemUuid": "opening"
        },
        "notes": ""
      },
      {
        "op": "selection_changed",
        "purpose": "Shared selection was set",
        "fields": [
          [
            "itemUuid",
            "string",
            "Yes",
            "-",
            "Empty clears."
          ]
        ],
        "example": {
          "type": "doc_patch",
          "op": "selection_changed",
          "itemUuid": "opening"
        },
        "notes": ""
      },
      {
        "op": "show_mode_changed",
        "purpose": "Shared show-mode signal changed",
        "fields": [
          [
            "enabled",
            "boolean",
            "Yes",
            "-",
            "New signal."
          ]
        ],
        "example": {
          "type": "doc_patch",
          "op": "show_mode_changed",
          "enabled": true
        },
        "notes": ""
      },
      {
        "op": "locale_changed",
        "purpose": "Locale feedback",
        "fields": [
          [
            "locale",
            "string",
            "Yes",
            "-",
            "Effective connection choice or fallback."
          ]
        ],
        "example": {
          "type": "doc_patch",
          "op": "locale_changed",
          "locale": "en"
        },
        "notes": "set_locale replies only to caller; REST fallback update reaches only sockets without a personal connection choice."
      },
      {
        "op": "meter_hz_changed",
        "purpose": "Delivery-rate acknowledgment",
        "fields": [
          [
            "hz",
            "integer",
            "Yes",
            "-",
            "Effective rate, capped to server tick."
          ]
        ],
        "example": {
          "type": "doc_patch",
          "op": "meter_hz_changed",
          "hz": 10
        },
        "notes": "Direct reply to requesting socket only."
      },
      {
        "op": "prefs_changed",
        "purpose": "Signed-in person's profile changed",
        "fields": [
          [
            "prefs",
            "UserPrefs",
            "Yes",
            "-",
            "Full sparse profile."
          ]
        ],
        "example": {
          "type": "doc_patch",
          "op": "prefs_changed",
          "prefs": {
            "meterMode": "dBFS"
          }
        },
        "notes": "Only sockets belonging to that person receive it."
      },
      {
        "op": "waveform_ready",
        "purpose": "Queued/cached waveform available",
        "fields": [
          [
            "item_uuid",
            "string",
            "Yes",
            "-",
            "Identifier from queued request."
          ],
          [
            "(other fields)",
            "Waveform",
            "Yes",
            "-",
            "bucket_count/duration_ms/sample_rate/source_channels/channels."
          ]
        ],
        "example": {
          "type": "doc_patch",
          "op": "waveform_ready",
          "item_uuid": "opening",
          "bucket_count": 2,
          "duration_ms": 1000,
          "sample_rate": 48000,
          "source_channels": 1,
          "channels": [
            {
              "peak": [
                0.2,
                0.4
              ],
              "rms": [
                0.1,
                0.2
              ]
            }
          ]
        },
        "notes": "Broadcast to all sockets, not just the requester."
      },
      {
        "op": "waveform_failed",
        "purpose": "Queued waveform decode failed",
        "fields": [
          [
            "item_uuid",
            "string",
            "Yes",
            "-",
            "Identifier from queued request."
          ]
        ],
        "example": {
          "type": "doc_patch",
          "op": "waveform_failed",
          "item_uuid": "opening"
        },
        "notes": ""
      },
      {
        "op": "advance_pending",
        "purpose": "Sequencer started an inter-cue wait",
        "fields": [
          [
            "fromUuid",
            "string",
            "Yes",
            "-",
            "Outgoing cue item."
          ],
          [
            "dueInMs",
            "integer",
            "Yes",
            "-",
            "Delay in milliseconds."
          ]
        ],
        "example": {
          "type": "doc_patch",
          "op": "advance_pending",
          "fromUuid": "opening",
          "dueInMs": 2000
        },
        "notes": "Notification only; no periodic countdown or cancellation frame is guaranteed."
      },
      {
        "op": "custom_action_http",
        "purpose": "Sequencer asks connected clients to execute an HTTP action",
        "fields": [
          [
            "action",
            "CustomAction",
            "Yes",
            "-",
            "Original action, type=http-request."
          ]
        ],
        "example": {
          "type": "doc_patch",
          "op": "custom_action_http",
          "action": {
            "type": "http-request",
            "request": {
              "method": "POST",
              "url": "http://controller.local/cue",
              "contentType": "json",
              "body": {
                "cue": 1
              }
            }
          }
        },
        "notes": "Broadcast to all clients. The desktop handles it; multiple connected executors can issue duplicate requests. Third-party monitoring clients should ignore it unless intentionally acting as an executor."
      }
    ]
  }
}
