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

> Learn how API commands are registered in Music Assistant using @api_command or register_api_command, and understand the required_role authentication for admin user access.

- Repository: [Music Assistant/server](https://github.com/music-assistant/server)
- Tags: internals
- Published: 2026-06-17

---

**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`](https://github.com/music-assistant/server/blob/main/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`](https://github.com/music-assistant/server/blob/main/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`](https://github.com/music-assistant/server/blob/main/mass.py)) scans objects for these attributes:

```python
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`](https://github.com/music-assistant/server/blob/main/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`](https://github.com/music-assistant/server/blob/main/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`](https://github.com/music-assistant/server/blob/main/music_assistant/controllers/webserver/controller.py) validates permissions before invoking the handler:

```python
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

```python
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

```python
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

```json
{
  "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`](https://github.com/music-assistant/server/blob/main/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`](https://github.com/music-assistant/server/blob/main/music_assistant/constants.py)
- **Security enforcement**: The webserver controller in [`music_assistant/controllers/webserver/controller.py`](https://github.com/music-assistant/server/blob/main/music_assistant/controllers/webserver/controller.py) validates tokens via `get_authenticated_user` from [`music_assistant/helpers/jwt_auth.py`](https://github.com/music-assistant/server/blob/main/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`](https://github.com/music-assistant/server/blob/main/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`](https://github.com/music-assistant/server/blob/main/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.