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
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 callTrue— Public API, other plugins can call throughctx.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:
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 APIself.unregister_dynamic_api(name, *, version="1")— Unregister dynamic APIself.clear_dynamic_apis()— Clear all dynamic APIsawait self.sync_dynamic_apis(*, offline_reason="Dynamic API offline")— Sync dynamic APIs to main program
Dynamic Registration Example
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
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 APIawait self.ctx.api.get(plugin_id, api_name)— Get API informationawait self.ctx.api.list()— List all available APIsawait self.ctx.api.replace_dynamic_apis(components, offline_reason="...")— Replace dynamic APIs
Calling Example
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 resultQuery API Information
# 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 —
@APIat class definition →register_dynamic_api()at runtime (usually in on_load) - Conditional Exposure —
@APInot supported →register_dynamic_api()can be decided dynamically based on configuration - Unregistration —
@APInot supported →register_dynamic_api()can be dynamically unregistered via unregister - Synchronization —
@APIautomatic →register_dynamic_api()requires calling sync_dynamic_apis() - Use Cases —
@APIfor fixed APIs →register_dynamic_api()for APIs that need to be enabled/disabled on demand