Every app running on Qbix Server gets machine-readable API docs published at well-known URLs. No annotations required — though docblocks make the docs richer. Add a handler file, the specs update on the next request.
How it works
From handler file to published spec
The server walks your handlers/ directory tree. Each PHP file becomes an API endpoint. The path maps to the event name:
handlers/
chat/
join.php → event: "chat/join"
message.php → event: "chat/message"
users/
login.php → event: "users/login"
profile/
get.php → event: "users/profile/get"
For each handler file, the server extracts:
1. Event name ← from the file path (chat/join.php → "chat/join") 2. Description ← from the file's docblock (/** ... */) or first // comment 3. Parameters ← from @param tags in the docblock 4. Return type ← from @return tags 5. Tags/categories ← from the directory structure (chat/*, users/*)
This information is published at three well-known URLs, all generated from the same scan.
Supported comment formats
The parser supports three formats. Use whichever your codebase already uses — they can coexist in the same project.
PHPDoc (standard PHP):
/**
* Send a chat message to a room.
*
* Broadcasts to all room members except the sender.
*
* @param string $text The message content
* @param string $room The room to send to
* @param ?string $name Display name (optional, nullable)
* @return array {id: int, broadcast: bool}
* @throws InvalidArgumentException If text is empty
*/
YUIDoc / Qbix style (used throughout the Qbix Platform):
/**
* Send a chat message to a room.
*
* @param {String} text The message content
* @param {String} room The room to send to
* @param {String} [name] Optional — display name
* @param {String} [format=text] Optional with default
* @return {Object} The broadcast result
*/
YUIDoc alternate (name before type):
/**
* @param text {String} The message content
* @param [name] {String} Optional display name
*/
Plain // comments (no docblock at all):
// Send a chat message to a room
function chat_message(&$params, &$result) {
// ...
}
The first // comment line becomes the summary. No parameter docs, but the handler still appears in the spec with its event name.
All recognized tags
| Tag | Formats | Maps to |
|---|---|---|
@param | @param type $name desc (PHPDoc)@param {Type} name desc (YUIDoc)@param {Type} [name] desc (optional)@param {Type} [name=default] (default)@param name {Type} desc (alternate)@param $name desc (bare) | OpenAPI requestBody schema properties |
@return / @returns | @return type desc (PHPDoc)@return {Type} desc (YUIDoc/JSDoc) | Response schema |
@throws / @throw | Same as @return | Error responses |
@method | @method GET | HTTP method hint |
@deprecated | @deprecated | OpenAPI deprecated flag |
@private / @internal | @private | Excluded from specs entirely |
@access private | @access private | Excluded from specs |
Nullable types: Both ?string and string|null are detected and mark the parameter as optional.
Array types: array<string> and string[] map to JSON Schema "type": "array".
Type mapping: string, int/integer, float/double/number, bool/boolean, array, object, mixed.
What the spec looks like (without any docblocks)
Even with zero comments, every handler file appears in the spec:
// handlers/chat/message.php — no docblock at all
function chat_message(&$params, &$result) {
$room = $params['room'];
Q_Socket::broadcast(['message', $params['data']]);
}
Generates this OpenAPI path:
{
"/Q/event/chat/message": {
"post": {
"summary": "chat/message",
"operationId": "chat_message",
"tags": ["chat"],
"requestBody": { "content": { "application/json": {
"schema": { "type": "object" }
}}}
}
}
}
Add a // Send a chat message at the top and the summary changes. Add a docblock with @param tags and the schema fills in. The more you document, the richer the spec — but it works from zero.
Three specs from one scan
/.well-known/openapi.json — OpenAPI 3.1
Standard API spec. Works with every tool in the ecosystem:
| Tool | How to use |
|---|---|
| Postman | Import → paste URL → complete collection with all your endpoints |
| Swagger UI | Point the explorer at it → interactive try-it-out docs |
| Redoc | Feed it the URL → polished static reference |
| Insomnia | Import → OpenAPI → all requests pre-configured |
| Code generators | openapi-generator → typed client SDKs in any language |
The spec includes built-in server endpoints (/Q/health, /Q/event, /Q/stats) alongside your app’s handlers. Endpoints are tagged by directory — chat/* handlers get the “chat” tag, users/* get “users”.
/.well-known/mcp.json — MCP for AI tools
Each handler becomes an MCP tool that AI assistants can call directly:
{
"tools": [
{"name": "health", "description": "Check server health and uptime"},
{"name": "chat_join", "description": "Send a user into a chat room"},
{"name": "chat_message", "description": "Send a chat message to a room"},
{"name": "users_login", "description": "Handle user login"}
]
}
Claude, GPT, Cursor, or any MCP-compatible tool can discover your handlers, read their parameter schemas, and call them — no glue code, no adapters, no manual tool definitions. Descriptions come from your docblocks; parameters from @param tags.
/.well-known/qbix.json — Federation manifest
Other Qbix servers use this for federation — pin the fingerprint, discover endpoints, forward events:
{
"server": "Qbix Server",
"version": "1.1.0",
"fingerprint": "4af468e461fc2022...",
"endpoints": {
"event": "/Q/event",
"health": "/Q/health",
"openapi": "/.well-known/openapi.json",
"mcp": "/.well-known/mcp.json",
"websocket": "/Q/ws"
},
"plugins": [{"name": "Users", "version": "1.0"}]
}
What this means for your app
You write a handler file. The server publishes it as an OpenAPI endpoint, an MCP tool, and a federation target. Postman can import your API. AI assistants can call it. Other Qbix servers can forward events to it. Three audiences, zero configuration.