Skip to content

API Component

The @API decorator is used to declare API interfaces for inter-plugin communication. Other plugins can call these APIs through ctx.api.call(), enabling functional interoperability between plugins.

Decorator Signature

python
from maibot_sdk import API

@API(
    name: str,                  # API name (required)
    description: str = "",      # API description
    version: str = "1",         # API version
    public: bool = False,       # Whether to allow other plugins to call
    **metadata,                 # Additional metadata
)

Parameter Description

name

Unique identifier name for the API. Duplicate API names are not allowed within the same plugin. Other plugins locate and call APIs through plugin ID + API name.

public

  • False (default) — Only visible within the plugin, other plugins cannot call
  • True — Public API, other plugins can call through ctx.api.call()

version

API version number, defaults to "1". Used for API version management, can increment version number when incompatible updates are needed.

Static API Example

Declare APIs directly on the plugin class using the @API decorator:

python
from maibot_sdk import MaiBotPlugin, API


class RenderPlugin(MaiBotPlugin):
    async def on_load(self) -> None:
        self.ctx.logger.info("Render plugin loaded")

    async def on_unload(self) -> None:
        self.ctx.logger.info("Render plugin unloaded")

    async def on_config_update(self, scope: str, config_data: dict, version: str) -> None:
        pass

    @API(
        "render_html",
        description="Render HTML to image",
        version="1",
        public=True,
    )
    async def handle_render_html(self, html: str, **kwargs):
        # Call rendering capability
        result = await self.ctx.render.html2png(html)
        return {"success": True, "image_path": result}

    @API(
        "get_stats",
        description="Get rendering statistics",
        version="1",
        public=True,
    )
    async def handle_get_stats(self, **kwargs):
        return {
            "total_renders": 42,
            "avg_time_ms": 150,
        }

Dynamic API Registration

In addition to using the @API decorator for static declaration, APIs can also be registered and unregistered dynamically at runtime. Dynamic APIs are suitable for scenarios where APIs need to be exposed based on configuration or runtime conditions.

Dynamic API Methods

  • self.register_dynamic_api(name, handler, *, description, version, public, handler_name, **metadata) — Register dynamic API
  • self.unregister_dynamic_api(name, *, version="1") — Unregister dynamic API
  • self.clear_dynamic_apis() — Clear all dynamic APIs
  • await self.sync_dynamic_apis(*, offline_reason="Dynamic API offline") — Sync dynamic APIs to main program

Dynamic Registration Example

python
from maibot_sdk import MaiBotPlugin, PluginConfigBase, Field
from typing import Any


class DynamicApiPlugin(MaiBotPlugin):
    class MyConfig(PluginConfigBase):
        enable_translate: bool = Field(default=False, description="Whether to enable translation API")

    config_model = MyConfig

    async def on_load(self) -> None:
        # Dynamically register APIs based on configuration
        if self.config.enable_translate:
            self.register_dynamic_api(
                "translate",
                self._handle_translate,
                description="Text translation",
                version="1",
                public=True,
                handler_name="handle_translate",
            )

            self.register_dynamic_api(
                "detect_language",
                self._handle_detect_language,
                description="Language detection",
                version="1",
                public=True,
            )

        # Sync dynamic APIs to main program
        await self.sync_dynamic_apis()
        self.ctx.logger.info("Dynamic APIs synced")

    async def on_unload(self) -> None:
        # Clear and sync offline
        self.clear_dynamic_apis()
        await self.sync_dynamic_apis(offline_reason="Plugin unloaded")

    async def on_config_update(self, scope: str, config_data: dict, version: str) -> None:
        pass

    async def _handle_translate(self, text: str, target_lang: str = "en", **kwargs) -> dict[str, Any]:
        # Translation logic
        return {"translated": f"[translated {text} to {target_lang}]"}

    async def _handle_detect_language(self, text: str, **kwargs) -> dict[str, Any]:
        # Language detection logic
        return {"language": "zh", "confidence": 0.95}

Dynamic Unregistration Example

python
class ManagedApiPlugin(MaiBotPlugin):
    async def on_load(self) -> None:
        # Batch registration
        for name, handler in [("api_a", self._a), ("api_b", self._b)]:
            self.register_dynamic_api(name, handler, public=True)
        await self.sync_dynamic_apis()

    async def disable_api_b(self) -> None:
        # Unregister single API
        self.unregister_dynamic_api("api_b")
        await self.sync_dynamic_apis(offline_reason="API B disabled")

    async def on_unload(self) -> None:
        self.clear_dynamic_apis()
        await self.sync_dynamic_apis(offline_reason="Plugin unloaded")

    async def on_config_update(self, scope: str, config_data: dict, version: str) -> None:
        pass

    async def _a(self, **kwargs):
        return {"result": "a"}

    async def _b(self, **kwargs):
        return {"result": "b"}

Calling Other Plugins' APIs

Query and call other plugins' public APIs through the self.ctx.api proxy.

ctx.api Methods

  • await self.ctx.api.call(plugin_id, api_name, **kwargs) — Call other plugin's API
  • await self.ctx.api.get(plugin_id, api_name) — Get API information
  • await self.ctx.api.list() — List all available APIs
  • await self.ctx.api.replace_dynamic_apis(components, offline_reason="...") — Replace dynamic APIs

Calling Example

python
from maibot_sdk import MaiBotPlugin, Tool
from maibot_sdk.types import ToolParameterInfo, ToolParamType


class CallerPlugin(MaiBotPlugin):
    async def on_load(self) -> None:
        # List all available APIs
        apis = await self.ctx.api.list()
        self.ctx.logger.info("Available APIs: %s", apis)

    async def on_unload(self) -> None:
        pass

    async def on_config_update(self, scope: str, config_data: dict, version: str) -> None:
        pass

    @Tool(
        "translate_text",
        brief_description="Translate text",
        detailed_description="Parameter description:\n- text: string, required. Text to be translated.",
        parameters=[
            ToolParameterInfo(
                name="text",
                param_type=ToolParamType.STRING,
                description="Text to be translated",
                required=True,
            ),
        ],
    )
    async def handle_translate(self, text: str, **kwargs):
        # Call other plugin's translation API
        result = await self.ctx.api.call(
            "com.example.translate",   # Target plugin ID
            "translate",                # API name
            text=text,
            target_lang="en",
        )
        return result

Query API Information

python
# Get detailed information of specific API
api_info = await self.ctx.api.get("com.example.translate", "translate")
self.ctx.logger.info("API info: %s", api_info)

API Design Recommendations

Naming Conventions

  • Use lowercase letters and underscores: render_html, get_stats
  • Start with verbs: get_, render_, detect_, translate_
  • Avoid overly generic names, should reflect functional meaning

Version Management

  • Use "1" for initial version
  • Increment version number for incompatible updates
  • Multiple versions of the same name API can coexist

Parameter Design

  • API processor methods receive **kwargs, extract parameters from it
  • Required parameters should be explicitly declared as positional parameters
  • Return values should be serializable dictionaries

Static API vs Dynamic API Comparison

  • Declaration Timing@API at class definition → register_dynamic_api() at runtime (usually in on_load)
  • Conditional Exposure@API not supported → register_dynamic_api() can be decided dynamically based on configuration
  • Unregistration@API not supported → register_dynamic_api() can be dynamically unregistered via unregister
  • Synchronization@API automatic → register_dynamic_api() requires calling sync_dynamic_apis()
  • Use Cases@API for fixed APIs → register_dynamic_api() for APIs that need to be enabled/disabled on demand