How API Commands Are Registered in Music Assistant: The required_role Authentication System Explained

API commands in Music Assistant are registered either via the @api_command decorator or explicit calls to mass.register_api_command, with access controlled by a required_role attribute that enforces "admin" or "user" privileges through the JSON-RPC controller.

Music Assistant exposes its functionality through a JSON-RPC API built dynamically from command handlers. According to the music-assistant/server source code, this system supports two distinct registration patterns and implements role-based security via attributes stored on APICommandHandler objects. Understanding these mechanisms is essential for developers extending the server through custom providers or core modifications.

How API Commands Are Registered in Music Assistant

The server discovers API endpoints by scanning for specially marked callables during initialization. All registration paths ultimately create an APICommandHandler instance defined in music_assistant/helpers/api.py, which stores the command path, function signature, type hints, target callable, and security requirements.

Decorator-Based Registration

The most common approach uses the api_command decorator defined in music_assistant/helpers/api.py (lines 364-379). This decorator attaches three attributes to the function:

  • api_cmd: The command path string (e.g., "players/cmd/play")
  • api_authenticated: Boolean indicating if authentication is required
  • api_required_role: The role string "admin", "user", or None

When a Mass instance initializes, the _register_api_commands method (around line 880 of mass.py) scans objects for these attributes:

for obj in (self, *objects):
    if hasattr(obj, "api_cmd"):
        command = obj.api_cmd
        authenticated = getattr(obj, "api_authenticated", True)
        required_role = getattr(obj, "api_required_role", None)
        self.register_api_command(command, obj, authenticated, required_role, alias=False)

Explicit Runtime Registration

Providers and extensions can manually register commands by calling Mass.register_api_command (lines 683-707 of mass.py). This method builds an APICommandHandler via APICommandHandler.parse and stores it in self.command_handlers. This approach is useful when the handler is not a method of a class being scanned or when commands need to be added dynamically after initialization.

The required_role Authentication System

The required_role system provides fine-grained access control beyond simple authentication. Each APICommandHandler stores security metadata in the following attributes:

  • authenticated: If True, a valid Bearer token is required
  • required_role: If set to "admin" or "user", the caller must possess that specific role

The UserRole enum is defined in music_assistant/constants.py, with values ADMIN and USER.

Enforcement in the Webserver Controller

When a JSON-RPC request arrives, the controller in music_assistant/controllers/webserver/controller.py validates permissions before invoking the handler:

if handler.authenticated or handler.required_role:
    user = await get_authenticated_user(request)   # raises 401 if no token

    if handler.required_role == "admin" and user.role != UserRole.ADMIN:
        return web.Response(status=403, text="Admin access required")

If required_role is "user", any authenticated account suffices. If required_role is None but authenticated is True, any valid user may call the endpoint. If both are false, the endpoint is public.

Code Examples

Creating a Command with the api_command Decorator

from music_assistant.helpers.api import api_command

class MyExtension:
    def __init__(self, mass):
        self.mass = mass
        # registers all @api_command methods on this instance

        self.mass._register_api_commands([self])

    @api_command("my_extension/hello", authenticated=True, required_role="user")
    async def hello(self, name: str) -> dict:
        """Return a friendly greeting."""
        return {"message": f"Hello, {name}!"}

When MyExtension is instantiated, the decorator has already marked hello with api_cmd = "my_extension/hello", api_authenticated = True, and api_required_role = "user". The _register_api_commands method detects these attributes and creates the handler.

Manual Registration for Providers

class SomeProvider:
    def __init__(self, mass):
        self.mass = mass
        # manual registration – useful when the handler isn't a method

        self.mass.register_api_command(
            "some_provider/do_something",
            self.do_something,
            authenticated=True,
            required_role="admin",
        )

    async def do_something(self, payload: dict) -> dict:
        # implementation …

        return {"status": "ok"}

JSON-RPC Client Request Format

{
  "jsonrpc": "2.0",
  "id": "1",
  "command": "my_extension/hello",
  "args": {"name": "Alice"}
}

Requests to admin-only endpoints like "some_provider/do_something" without admin privileges return HTTP 403, while missing tokens return HTTP 401.

Summary

  • Two registration methods: Use the @api_command decorator for class methods, or call mass.register_api_command for explicit runtime registration
  • Handler storage: All commands become APICommandHandler objects stored in mass.command_handlers, defined in music_assistant/helpers/api.py
  • Role hierarchy: The required_role attribute accepts "admin" or "user" (or None), checked against the UserRole enum in music_assistant/constants.py
  • Security enforcement: The webserver controller in music_assistant/controllers/webserver/controller.py validates tokens via get_authenticated_user from music_assistant/helpers/jwt_auth.py and returns HTTP 401 for missing auth or HTTP 403 for insufficient roles
  • Public endpoints: Set authenticated=False and required_role=None to allow unauthenticated access

Frequently Asked Questions

What is the difference between authenticated and required_role in Music Assistant?

The authenticated boolean determines if any valid Bearer token must be present, while required_role specifies the privilege level required. If authenticated is True but required_role is None, any logged-in user can access the endpoint. If required_role is "admin", only administrators may proceed, regardless of general authentication status.

How do I create an admin-only API endpoint in Music Assistant?

Apply the decorator with required_role="admin" or call register_api_command with the same parameter. For example: @api_command("config/system", authenticated=True, required_role="admin"). The controller will reject non-admin users with HTTP 403 according to the checks in music_assistant/controllers/webserver/controller.py.

Can I register API commands outside of provider classes?

Yes. While providers typically register during load_provider, any code with access to the Mass instance can call mass.register_api_command directly. This is useful for dynamically loaded extensions or conditional feature registration that doesn't fit the standard provider lifecycle.

Where does Music Assistant validate the Bearer token?

Token validation occurs in music_assistant/helpers/jwt_auth.py via the get_authenticated_user function. The webserver controller calls this function when handler.authenticated or handler.required_role is set, and it raises a 401 error if the token is missing or invalid before role checking proceeds.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →