# How API Commands Are Registered and Handled in the Music Assistant Server

> Learn how Music Assistant server registers and handles API commands. Discover the @api_command decorator, automatic type conversion, and role-based authentication for efficient API management.

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

---

**API commands in the Music Assistant server are registered using the `@api_command` decorator, scanned at startup into a command handler registry, and dispatched via WebSocket with automatic type conversion and role-based authentication.**

The Music Assistant server implements a declarative registration mechanism that bridges internal Python functions to external WebSocket API endpoints. This system allows developers to expose functionality using minimal boilerplate while the runtime handles routing, parameter validation, and security enforcement. Understanding how commands are registered and handled is essential for extending the server or debugging API interactions.

## Declaring API Commands with the @api_command Decorator

Commands are declared using 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 64-71). This decorator attaches metadata to function objects without altering their behavior:

```python
@api_command("info")
def get_server_info(self) -> ServerInfoMessage:
    """Return Info of this server."""
    ...

```

The decorator stores three key attributes on the function object:
- `api_cmd` – The command string identifier (e.g., `"info"`, `"providers/manifests"`)
- `api_authenticated` – Boolean indicating if the command requires authentication (defaults to `True`)
- `api_required_role` – Optional role string (e.g., `"admin"`) for authorization checks

## Scanning and Registering Commands at Runtime

When the server starts, `MusicAssistant.start()` invokes `_register_api_commands()` in [`music_assistant/mass.py`](https://github.com/music-assistant/server/blob/main/music_assistant/mass.py) (lines 80-13) to discover and register all decorated methods:

```python
for cls in (self, self.config, self.metadata, ...):
    for attr_name in dir(cls):
        obj = getattr(cls, attr_name)
        if hasattr(obj, "api_cmd"):
            authenticated = getattr(obj, "api_authenticated", True)
            required_role = getattr(obj, "api_required_role", None)
            self.register_api_command(obj.api_cmd, obj, authenticated, required_role)

```

The scanner iterates through a hardcoded tuple of core objects—including the main `MusicAssistant` instance, config controller, and metadata controller—checking each attribute for the `api_cmd` marker. Valid commands are passed to `register_api_command()` (lines 84-09), which instantiates an `APICommandHandler` and stores it in `self.command_handlers`, a dictionary mapping command strings to their handlers.

## The APICommandHandler Model

The `APICommandHandler` class in [`music_assistant/helpers/api.py`](https://github.com/music-assistant/server/blob/main/music_assistant/helpers/api.py) (lines 70-62) encapsulates the metadata and execution logic for each command. Its static method `parse()` performs reflection to prepare the handler:

- Extracts the function signature using `inspect.signature`
- Resolves type hints (including forward references) via `_get_type_hints_for_api_command`
- Normalizes generic `TypeVar` instances (e.g., `ItemCls` to concrete media-item classes)
- Stores authentication flags and command aliases

This metadata enables the server to validate and convert incoming JSON arguments to the correct Python types at runtime.

## Dispatching and Executing Commands

Incoming WebSocket messages are handled by `WebsocketClientHandler._handle_command()` in [`music_assistant/controllers/webserver/websocket_client.py`](https://github.com/music-assistant/server/blob/main/music_assistant/controllers/webserver/websocket_client.py) (lines 92-41). The dispatch flow follows these steps:

1. **Command lookup** – The handler retrieves the command string from the message and looks it up in `self.mass.command_handlers`. If not found, an `InvalidCommand` error is returned.

2. **Authentication checks** – If the handler requires authentication (`handler.authenticated`) or a specific role (`handler.required_role`), the method validates the client's stored user (`self._authenticated_user`). Failures raise `AuthenticationRequired` or `InsufficientPermissions`.

3. **Task creation** – Valid commands are scheduled as background tasks:
   ```python
   self.mass.create_task(self._run_handler(handler, msg))
   ```

The `_run_handler()` method (lines 45-62) executes the actual logic:
- Parses arguments using `parse_arguments()` from [`helpers/api.py`](https://github.com/music-assistant/server/blob/main/helpers/api.py), converting JSON values to enums, dates, or model objects
- Invokes the target function
- Handles three return types: synchronous values, coroutines (awaited), and async generators (streamed in chunks)
- Sends the result wrapped in a `SuccessResultMessage`

## Error Handling and Security

Error handling occurs within `_run_handler()` (lines 63-86). The implementation distinguishes between known `MusicAssistantError` subclasses and unexpected exceptions:

- **Known errors** – Caught, logged at warning level, and returned with their specific error code and translation key
- **Unexpected errors** – Logged at debug level (if enabled) and returned as a generic `999` error to prevent information leakage

This architecture ensures that authentication and authorization checks occur before execution, while type safety is enforced through the signature inspection performed during handler construction.

## Practical Implementation Examples

### Declaring a Protected Admin Command

```python
from music_assistant.helpers.api import api_command

class PlayerController:
    @api_command("players/rename", required_role="admin")
    async def rename_player(self, player_id: str, name: str) -> bool:
        """Rename a player (admin only)."""
        player = self.mass.get_player(player_id)
        await player.rename(name)
        return True

```

This example registers the method under the `"players/rename"` path and restricts access to users with the admin role.

### Manual Runtime Registration

For dynamic scenarios, you can bypass the decorator and register commands directly:

```python
def my_dynamic_handler(user_id: str) -> dict:
    return {"user_id": user_id, "info": "dynamic"}

# Register at runtime

mass.register_api_command(
    command="dynamic/userinfo",
    handler=my_dynamic_handler,
    authenticated=True,
    required_role=None,
)

```

This uses `MusicAssistant.register_api_command()` directly, allowing plugins to extend the API without modifying core controllers.

## Summary

- **Use `@api_command`** in [`music_assistant/helpers/api.py`](https://github.com/music-assistant/server/blob/main/music_assistant/helpers/api.py) to declare API endpoints with metadata for authentication and roles
- **Registration occurs at startup** via `MusicAssistant._register_api_commands()` in [`mass.py`](https://github.com/music-assistant/server/blob/main/mass.py), which scans core objects for decorated methods
- **Handlers are stored** in the `self.command_handlers` dictionary, mapping command strings to `APICommandHandler` instances
- **WebSocket dispatch** in [`websocket_client.py`](https://github.com/music-assistant/server/blob/main/websocket_client.py) handles lookup, authentication, and execution as asynchronous tasks
- **Automatic type conversion** converts incoming JSON to Python types using the function signature captured during registration
- **Error handling** distinguishes between application errors (with codes) and unexpected exceptions (generic responses)

## Frequently Asked Questions

### How do I create a new API command in Music Assistant?

Define a method in a controller class and decorate it with `@api_command("command/name")` from [`music_assistant/helpers/api.py`](https://github.com/music-assistant/server/blob/main/music_assistant/helpers/api.py). The server automatically discovers and registers the method during startup. Optionally specify `required_role="admin"` if the command should be restricted to administrators.

### What authentication options are available for API commands?

The `@api_command` decorator accepts two security parameters: `authenticated` (boolean, defaults to `True`) and `required_role` (string, defaults to `None`). When `authenticated=True`, the WebSocket handler verifies the client has completed authentication. If `required_role` is set, the handler checks the user's role against the requirement before executing the command.

### How does the server handle API command errors?

The `_run_handler()` method in [`websocket_client.py`](https://github.com/music-assistant/server/blob/main/websocket_client.py) catches `MusicAssistantError` subclasses and returns them with specific error codes and translation keys. Unexpected exceptions are caught and returned as generic `999` errors to avoid exposing internal details, with full tracebacks available in debug logs when enabled.

### Can I register API commands dynamically at runtime?

Yes, though this is rare. Call `mass.register_api_command()` directly with the command string, handler function, and authentication flags. This bypasses the decorator scan and is useful for plugins that need to register commands after the initial startup sequence has completed.