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 requiredapi_required_role: The role string"admin","user", orNone
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: IfTrue, a valid Bearer token is requiredrequired_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_commanddecorator for class methods, or callmass.register_api_commandfor explicit runtime registration - Handler storage: All commands become
APICommandHandlerobjects stored inmass.command_handlers, defined inmusic_assistant/helpers/api.py - Role hierarchy: The
required_roleattribute accepts"admin"or"user"(orNone), checked against theUserRoleenum inmusic_assistant/constants.py - Security enforcement: The webserver controller in
music_assistant/controllers/webserver/controller.pyvalidates tokens viaget_authenticated_userfrommusic_assistant/helpers/jwt_auth.pyand returns HTTP 401 for missing auth or HTTP 403 for insufficient roles - Public endpoints: Set
authenticated=Falseandrequired_role=Noneto 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →