Complete reference for BotNexus REST API endpoints, including agents, sessions, providers, skills, and system status.
- Overview
- Authentication
- Agent Management
- Skills Management
- Channels Management
- Extensions Management
- Chat
- Commands
- Session Management
- System & Status
- Error Handling
Base URL: http://localhost:5005/api
All endpoints follow REST conventions and return JSON responses. The default port is 5005 (configurable via config.json).
Authentication: All endpoints require API key authentication (see Authentication below).
Include your API key in the X-Api-Key request header:
GET /api/agents
X-Api-Key: your-api-key-hereOr pass it as a query parameter:
GET /api/agents?apiKey=your-api-key-hereExemptions: /health and /swagger are exempt from authentication. The Blazor WebUI is also exempt when running in development mode.
Every request/response carries a correlation identifier for end-to-end tracing:
- Request: Optionally include
X-Correlation-Idto propagate your own trace ID. - Response: The header is always returned. If not provided on the request, the server generates a new UUID.
GET /api/agents
X-Api-Key: your-api-key
X-Correlation-Id: my-trace-id-123Use this header to correlate client requests with server-side logs.
HTTP REST requests are rate-limited per client (authenticated caller ID or IP address). Default: 60 requests per 60-second window (configurable via gateway.rateLimit in config.json).
When the limit is exceeded, the server returns:
| Status | Header | Description |
|---|---|---|
429 Too Many Requests |
Retry-After |
Seconds until the current window resets |
The /health endpoint is exempt from rate limiting.
Configuration:
{
"gateway": {
"rateLimit": {
"requestsPerMinute": 60,
"windowSeconds": 60
}
}
}Cross-Origin Resource Sharing is environment-aware:
| Environment | Behavior |
|---|---|
| Development | All origins, methods, and headers allowed (AllowAny*) |
| Production | Origins restricted to gateway.cors.allowedOrigins config (defaults to http://localhost:5005); methods restricted to GET, POST, PUT, DELETE, OPTIONS |
Configure production origins in config.json:
{
"gateway": {
"cors": {
"allowedOrigins": ["https://your-domain.com"]
}
}
}Endpoint: GET /api/agents
Description: Retrieve a list of all configured agents.
Request:
GET /api/agents
X-Api-Key: your-api-keyResponse: 200 OK
[
{
"agentId": "assistant",
"displayName": "Assistant",
"modelId": "gpt-4.1",
"apiProvider": "copilot",
"systemPrompt": null,
"isolationStrategy": "in-process",
"toolIds": [],
"subAgentIds": [],
"maxConcurrentSessions": 0
},
{
"agentId": "analyzer",
"displayName": "Analyzer",
"modelId": "claude-sonnet-4-5",
"apiProvider": "anthropic",
"systemPrompt": null,
"isolationStrategy": "in-process",
"toolIds": [],
"subAgentIds": [],
"maxConcurrentSessions": 0
}
]Endpoint: GET /api/agents/{agentId}
Description: Retrieve a specific agent by ID.
Parameters:
agentId(string, path) — Agent ID
Request:
GET /api/agents/my-agent
X-Api-Key: your-api-keyResponse: 200 OK
{
"agentId": "my-agent",
"displayName": "My Agent",
"modelId": "gpt-4.1",
"apiProvider": "copilot",
"systemPrompt": "You are helpful",
"isolationStrategy": "in-process",
"toolIds": [],
"subAgentIds": [],
"maxConcurrentSessions": 0
}Error Responses:
404 Not Found— Agent does not exist
Endpoint: POST /api/agents
Description: Create a new agent with the specified configuration.
Request Body:
{
"agentId": "my-agent",
"displayName": "My Agent",
"modelId": "gpt-4.1",
"apiProvider": "copilot",
"systemPrompt": "You are a helpful assistant",
"isolationStrategy": "in-process"
}Field Descriptions:
agentId(string, required) — Unique agent identifierdisplayName(string, required) — Human-readable display namemodelId(string, required) — Model identifier (e.g., "gpt-4.1", "claude-sonnet-4-5")apiProvider(string, required) — Provider name (e.g., "copilot", "openai", "anthropic")systemPrompt(string, optional) — System instruction for the agentsystemPromptFile(string, optional) — Path to an external system prompt fileisolationStrategy(string, optional) — Execution strategy (default: "in-process")toolIds(array of strings, optional) — Tool identifiers the agent can accesssubAgentIds(array of strings, optional) — Agent IDs this agent can call as sub-agentsmaxConcurrentSessions(integer, optional) — Max concurrent sessions (0 = unlimited)
Request:
POST /api/agents
X-Api-Key: your-api-key
Content-Type: application/json
{
"agentId": "analyzer",
"displayName": "Analyzer",
"modelId": "gpt-4.1",
"apiProvider": "copilot",
"systemPrompt": "Analyze data professionally",
"isolationStrategy": "in-process"
}Response: 201 Created
{
"agentId": "analyzer",
"displayName": "Analyzer",
"modelId": "gpt-4.1",
"apiProvider": "copilot",
"systemPrompt": "Analyze data professionally",
"isolationStrategy": "in-process",
"toolIds": [],
"subAgentIds": [],
"maxConcurrentSessions": 0
}Side Effects:
- Config is backed up to
config.json.bak - Agent workspace is bootstrapped with template files (SOUL.md, IDENTITY.md, etc.)
- Agent name is normalized to lowercase with dashes (e.g., "My Agent" → my-agent")
Error Responses:
400 Bad Request— Invalid configuration or duplicate agent name500 Internal Server Error— Workspace creation failed
Endpoint: PUT /api/agents/{agentId}
Description: Update an existing agent descriptor in-place. If the request body omits agentId, the route value is used. If both are provided, they must match — a mismatch returns 400 Bad Request.
Parameters:
agentId(string, path) — The registered agent ID to update
Request Body: Same schema as Create Agent.
Request:
PUT /api/agents/my-agent
X-Api-Key: your-api-key
Content-Type: application/json
{
"agentId": "my-agent",
"displayName": "My Updated Agent",
"modelId": "claude-sonnet-4-5",
"apiProvider": "anthropic",
"systemPrompt": "You are a refined assistant"
}Response: 200 OK
{
"agentId": "my-agent",
"displayName": "My Updated Agent",
"modelId": "claude-sonnet-4-5",
"apiProvider": "anthropic",
"systemPrompt": "You are a refined assistant",
"isolationStrategy": "in-process",
"toolIds": [],
"subAgentIds": [],
"maxConcurrentSessions": 0
}Error Responses:
400 Bad Request— BodyagentIddoes not match the routeagentId404 Not Found— Agent with the given ID is not registered
Endpoint: DELETE /api/agents/{agentId}
Description: Unregister an agent.
Parameters:
agentId(string, path) — Agent ID
Request:
DELETE /api/agents/my-agent
X-Api-Key: your-api-keyResponse: 204 No Content
Note: This operation is idempotent — no error is returned if the agent does not exist.
Endpoint: GET /api/agents/instances
Description: List all active agent instances (running agents with live sessions).
Request:
GET /api/agents/instances
X-Api-Key: your-api-keyResponse: 200 OK
[
{
"agentId": "assistant",
"sessionId": "abc123",
"status": "running",
"startedAt": "2026-01-15T10:30:00Z"
}
]Endpoint: GET /api/agents/{agentId}/sessions/{sessionId}/status
Description: Check the status of a specific running agent instance.
Parameters:
agentId(string, path) — Agent IDsessionId(string, path) — Session ID
Request:
GET /api/agents/assistant/sessions/abc123/status
X-Api-Key: your-api-keyResponse: 200 OK — Returns the agent instance details.
Error Responses:
404 Not Found— No active instance for the given agent/session
Endpoint: POST /api/agents/{agentId}/sessions/{sessionId}/stop
Description: Stop a running agent instance.
Parameters:
agentId(string, path) — Agent IDsessionId(string, path) — Session ID
Request:
POST /api/agents/assistant/sessions/abc123/stop
X-Api-Key: your-api-keyResponse: 204 No Content
Error Responses:
404 Not Found— No active instance for the given agent/session
Skills are modular knowledge packages that enhance agent reasoning. Learn more in the Skills Guide.
Note: Skills endpoints are provided by the main BotNexus application host, not the Gateway API project. They are included here for completeness.
Endpoint: GET /api/skills
Description: Retrieve all global skills available to all agents.
Request:
GET /api/skills
X-Api-Key: your-api-keyResponse: 200 OK
[
{
"name": "git-workflow",
"description": "Git workflow and commit conventions for BotNexus",
"version": "1.0.0",
"scope": "Global",
"alwaysLoad": false,
"sourcePath": "/home/user/.botnexus/skills/git-workflow/SKILL.md"
},
{
"name": "testing-standards",
"description": "Testing patterns and best practices",
"version": "1.0.0",
"scope": "Global",
"alwaysLoad": false,
"sourcePath": "/home/user/.botnexus/skills/testing-standards/SKILL.md"
}
]Response Fields:
name(string) — Skill identifier (folder name)description(string) — Human-readable skill descriptionversion(string) — Semantic version of the skillscope(string) — Scope:"Global"or"Agent"alwaysLoad(boolean) — Reserved for future use (always false currently)sourcePath(string) — File path for debugging
Endpoint: GET /api/agents/{name}/skills
Description: Retrieve all skills (global + per-agent) loaded for a specific agent, respecting DisabledSkills configuration.
Parameters:
name(string, path) — Agent name
Request:
GET /api/agents/code-reviewer/skills
X-Api-Key: your-api-keyResponse: 200 OK
[
{
"name": "code-review-criteria",
"description": "Code review standards for this project",
"version": "1.0.0",
"scope": "Agent",
"alwaysLoad": false,
"sourcePath": "/home/user/.botnexus/agents/code-reviewer/skills/code-review-criteria/SKILL.md",
"contentPreview": "# Code Review Criteria\n\nReviewers should check:\n1. Functionality\n2. Code style\n3. Tests\n..."
},
{
"name": "git-workflow",
"description": "Git workflow and commit conventions for BotNexus",
"version": "1.0.0",
"scope": "Global",
"alwaysLoad": false,
"sourcePath": "/home/user/.botnexus/skills/git-workflow/SKILL.md"
}
]Response Fields:
- All fields from List Global Skills, plus:
contentPreview(string) — First 200 characters of skill markdown content (agent endpoint only)
Skill Resolution Order:
- Global skills from
~/.botnexus/skills/ - Per-agent skills from
~/.botnexus/agents/{name}/skills/(override global if same name) - Filtered by agent's
DisabledSkillsconfiguration - Sorted alphabetically by name
Error Responses:
404 Not Found— Agent does not exist
Notes:
- Agent skills override global skills with the same name
- Use
DisabledSkillsin agent config to exclude specific skills or patterns - See Skills Guide: Disabling Skills for pattern syntax
Added in Wave 1. Channels are adapter plugins that connect BotNexus to external surfaces (SignalR, Telegram, TUI, etc.).
Endpoint: GET /api/channels
Description: List all registered channel adapters and their runtime capabilities.
Request:
GET /api/channels
X-Api-Key: your-api-keyResponse: 200 OK
[
{
"name": "signalr",
"displayName": "SignalR",
"isRunning": true,
"supportsStreaming": true,
"supportsSteering": true,
"supportsFollowUp": true,
"supportsThinking": true,
"supportsToolDisplay": true
},
{
"name": "telegram",
"displayName": "Telegram",
"isRunning": false,
"supportsStreaming": false,
"supportsSteering": false,
"supportsFollowUp": false,
"supportsThinking": false,
"supportsToolDisplay": false
}
]Response Fields:
| Field | Type | Description |
|---|---|---|
name |
string | Channel type identifier (e.g., "signalr", "telegram", "tui") |
displayName |
string | Human-readable channel name |
isRunning |
boolean | Whether the adapter is currently active |
supportsStreaming |
boolean | Whether the adapter supports streamed content deltas |
supportsSteering |
boolean | Whether the adapter supports real-time steering messages |
supportsFollowUp |
boolean | Whether the adapter supports follow-up message queuing |
supportsThinking |
boolean | Whether the adapter renders thinking/progress output |
supportsToolDisplay |
boolean | Whether the adapter renders tool call activity |
Added in Wave 1. Extensions are assembly-based plugins loaded at runtime from
~/.botnexus/extensions/.
Endpoint: GET /api/extensions
Description: List all loaded runtime extensions and their declared types.
Request:
GET /api/extensions
X-Api-Key: your-api-keyResponse: 200 OK
[
{
"name": "GitHub Tools",
"version": "1.0.0",
"type": "tool",
"assemblyPath": "BotNexus.Extensions.GitHub.dll"
},
{
"name": "Custom Provider",
"version": "0.5.0",
"type": "provider",
"assemblyPath": "MyCustomProvider.dll"
}
]Response Fields:
| Field | Type | Description |
|---|---|---|
name |
string | Extension display name from the manifest |
version |
string | Extension version from the manifest |
type |
string | Extension type (e.g., "tool", "provider", "channel", "unknown") |
assemblyPath |
string | Entry assembly filename (not full path) |
Notes:
- An extension that declares multiple types appears once per type in the response.
- Extensions with no declared types use
"unknown"as the type.
Endpoint: POST /api/chat
Description: Send a message to an agent and receive the complete response (non-streaming). For real-time streaming, use the SignalR hub at /hub/gateway.
Request Body:
{
"agentId": "assistant",
"message": "Hello, how can you help?",
"sessionId": "optional-session-id"
}Field Descriptions:
agentId(string, required) — Target agent IDmessage(string, required) — Message content to sendsessionId(string, optional) — Session ID to continue a conversation. If omitted, a new session is created.
Request:
POST /api/chat
X-Api-Key: your-api-key
Content-Type: application/json
{
"agentId": "assistant",
"message": "What is BotNexus?"
}Response: 200 OK
{
"sessionId": "abc123",
"content": "BotNexus is a modular AI agent execution platform...",
"usage": {
"inputTokens": 50,
"outputTokens": 120
}
}Error Responses:
429 Too Many Requests— Agent concurrency limit exceeded
Endpoint: POST /api/chat/steer
Description: Inject a steering message into an agent's active run. This allows mid-stream guidance without aborting the current response.
Request Body:
{
"agentId": "assistant",
"sessionId": "abc123",
"message": "Focus on the security aspects"
}Request:
POST /api/chat/steer
X-Api-Key: your-api-key
Content-Type: application/json
{
"agentId": "assistant",
"sessionId": "abc123",
"message": "Focus on the security aspects"
}Response: 202 Accepted
Error Responses:
404 Not Found— Agent session not found429 Too Many Requests— Agent concurrency limit exceeded
Endpoint: POST /api/chat/follow-up
Description: Queue a follow-up message for an active agent session. The follow-up is processed after the current run completes.
Request Body:
{
"agentId": "assistant",
"sessionId": "abc123",
"message": "Also summarize the key points"
}Request:
POST /api/chat/follow-up
X-Api-Key: your-api-key
Content-Type: application/json
{
"agentId": "assistant",
"sessionId": "abc123",
"message": "Also summarize the key points"
}Response: 202 Accepted
Error Responses:
404 Not Found— Agent session not found429 Too Many Requests— Agent concurrency limit exceeded
The Commands API allows clients to discover and execute slash commands contributed by extensions and built-in handlers. Commands are backend-driven, enabling a unified command palette across WebUI, TUI, and future client surfaces.
Endpoint: GET /api/commands
Description: Retrieve all available commands (built-in + extension-contributed). Used by client surfaces to populate command palettes and autocomplete.
Request:
GET /api/commands
X-Api-Key: your-api-keyResponse: 200 OK
[
{
"name": "/help",
"description": "Show available commands",
"category": "System",
"clientSideOnly": false,
"subCommands": null
},
{
"name": "/agents",
"description": "List available agents",
"category": "System",
"clientSideOnly": false,
"subCommands": null
},
{
"name": "/skills",
"description": "Manage skills for the current agent",
"category": "Extension",
"clientSideOnly": false,
"subCommands": [
{
"name": "list",
"description": "Show loaded, available, and denied skills"
},
{
"name": "info",
"description": "Show skill metadata and size"
},
{
"name": "add",
"description": "Load a skill into the current session"
},
{
"name": "remove",
"description": "Unload a skill from the current session"
},
{
"name": "reload",
"description": "Re-discover skills from disk"
}
]
},
{
"name": "/reset",
"description": "Clear chat and reset current session",
"category": "System",
"clientSideOnly": true,
"subCommands": null
}
]Response Fields:
name(string) — Command name including slash (e.g., "/skills")description(string) — Short description for the command palettecategory(string) — Command category: "System" (built-in) or "Extension" (contributor)clientSideOnly(boolean) — If true, client handles execution without backend call (e.g.,/resetmanages DOM/reconnect). If false, execution requires POST to/api/commands/executesubCommands(array of objects, nullable) — Sub-command definitions (e.g., "/skills list", "/skills info <name>"). Null if command has no sub-commandsname(string) — Sub-command name (without slash)description(string) — Sub-command description
Notes:
- Client-side commands (where
clientSideOnly: true) execute locally without backend involvement - Backend commands are executed via
POST /api/commands/execute - Extensions register commands via
ICommandContributorinterface during gateway startup - Command name collisions are resolved by first-registered-wins; duplicates are logged as warnings
Endpoint: POST /api/commands/execute
Description: Execute a slash command and return the result. Routes to the appropriate handler (built-in or extension-contributed) based on command name.
Request Body:
{
"input": "/skills list",
"agentId": "my-agent",
"sessionId": "sess-123abc"
}Field Descriptions:
input(string, required) — Full command text including slash and arguments (e.g., "/skills list", "/skills info my-skill")agentId(string, optional) — Agent context for the command. Required for agent-aware commands like/skillssessionId(string, optional) — Session context for the command
Request:
POST /api/commands/execute
X-Api-Key: your-api-key
Content-Type: application/json
{
"input": "/skills list",
"agentId": "my-agent",
"sessionId": "sess-123abc"
}Response: 200 OK
{
"title": "📚 Skills for my-agent",
"body": "Loaded (3):\n ado-work-management Unified ADO work management...\n m365-communication Microsoft 365 communication...\n reference-bank Shared reference data...\n\nAvailable (8):\n calendar-interaction Calendar management...\n datetime-helper Date/time utilities...\n ...\n\nConfig: max 20 loaded, ~25K token budget, ~10.5K used",
"isError": false
}Response Fields:
title(string) — Display title for the result block (e.g., "📚 Skills for my-agent")body(string) — Result content (plain text, rendered in a preformatted block)isError(boolean) — True if the command execution failed. When true, the client should render the body as an error message
Example: /skills info
Request:
POST /api/commands/execute
X-Api-Key: your-api-key
Content-Type: application/json
{
"input": "/skills info ado-work-management",
"agentId": "my-agent",
"sessionId": "sess-123abc"
}Response: 200 OK
{
"title": "Skill: ado-work-management",
"body": "Name: ado-work-management\nDescription: Unified ADO work management...\nSource: Global (~/.botnexus/skills/ado-work-management/)\nStatus: Loaded (auto-load)\nSize: ~3,200 tokens\nLicense: MIT\nFiles: SKILL.md, reference/features.md, workflows/",
"isError": false
}Example: Error Response
Request:
POST /api/commands/execute
X-Api-Key: your-api-key
Content-Type: application/json
{
"input": "/skills add nonexistent-skill",
"agentId": "my-agent",
"sessionId": "sess-123abc"
}Response: 200 OK
{
"title": "Error",
"body": "Skill 'nonexistent-skill' not found. Available skills: ado-work-management, m365-communication, ...",
"isError": true
}Error Responses:
400 Bad Request— Malformed input (e.g., empty string, missing required fields)404 Not Found— Command not recognized (no handler found for the command name)400 Bad Request— Invalid arguments (e.g., sub-command not found for a command with sub-commands)
Notes:
- All command errors return HTTP 200 with
isError: truein the response body, not HTTP error codes. This allows clients to consistently handle command results (success or failure) in a unified way - Exception: Malformed requests or unrecognized commands return HTTP 4xx errors
- Built-in commands (e.g.,
/help,/status,/agents) are implemented in the Gateway as built-in handlers - Extension commands (e.g.,
/skills) are implemented by extensions that implement theICommandContributorinterface - Commands are agent-aware when applicable (e.g.,
/skills listshows skills for the specified agent) - Sub-command parsing is case-insensitive (e.g., "/skills LIST" and "/skills list" are equivalent)
Endpoint: GET /api/sessions
Description: Retrieve all conversation sessions.
Query Parameters:
agentId(string, optional) — Filter sessions by agent ID
Request:
GET /api/sessions
X-Api-Key: your-api-keyResponse: 200 OK
[
{
"sessionId": "abc123",
"agentId": "assistant",
"channelType": "signalr",
"callerId": "user-1",
"status": "Active",
"createdAt": "2026-01-15T10:30:00Z",
"updatedAt": "2026-01-15T11:45:00Z"
}
]Endpoint: GET /api/sessions/{sessionId}
Description: Retrieve a specific session by ID, including conversation history.
Parameters:
sessionId(string, path) — Session ID
Request:
GET /api/sessions/session-abc123
X-Api-Key: your-api-keyResponse: 200 OK — Returns the session with full history.
Error Responses:
404 Not Found— Session does not exist
Endpoint: GET /api/sessions/{sessionId}/history
Description: Get paginated session history for long-running conversations.
Parameters:
sessionId(string, path) — Session ID
Query Parameters:
offset(integer, optional, default: 0) — Zero-based offset into historylimit(integer, optional, default: 50, max: 200) — Number of entries to return
Request:
GET /api/sessions/session-abc123/history?offset=0&limit=50
X-Api-Key: your-api-keyResponse: 200 OK
{
"offset": 0,
"limit": 50,
"totalCount": 120,
"entries": [
{
"role": "user",
"content": "Hello",
"timestamp": "2026-01-15T10:30:00Z"
},
{
"role": "assistant",
"content": "Hi there!",
"timestamp": "2026-01-15T10:30:01Z"
}
]
}Error Responses:
400 Bad Request— Invalid offset or limit404 Not Found— Session does not exist
Endpoint: GET /api/sessions/{sessionId}/metadata
Description: Retrieve metadata key-value pairs for a session.
Parameters:
sessionId(string, path) — Session ID
Request:
GET /api/sessions/session-abc123/metadata
X-Api-Key: your-api-keyResponse: 200 OK
{
"environment": "production",
"userId": "user-42",
"customTag": "priority"
}Error Responses:
404 Not Found— Session does not exist
Endpoint: PATCH /api/sessions/{sessionId}/metadata
Description: Merge metadata entries into a session. Keys with null values are removed.
Parameters:
sessionId(string, path) — Session ID
Request:
PATCH /api/sessions/session-abc123/metadata
X-Api-Key: your-api-key
Content-Type: application/json
{
"environment": "staging",
"customTag": null
}Response: 200 OK — Returns the updated metadata dictionary.
Error Responses:
400 Bad Request— Request body is not a JSON object404 Not Found— Session does not exist
Endpoint: DELETE /api/sessions/{sessionId}
Description: Delete a session and its conversation history.
Parameters:
sessionId(string, path) — Session ID
Request:
DELETE /api/sessions/session-abc123
X-Api-Key: your-api-keyResponse: 204 No Content
Endpoint: PATCH /api/sessions/{sessionId}/suspend
Description: Suspend an active session. Only sessions with Active status can be suspended.
Parameters:
sessionId(string, path) — Session ID
Request:
PATCH /api/sessions/session-abc123/suspend
X-Api-Key: your-api-keyResponse: 200 OK — Returns the updated session.
Error Responses:
404 Not Found— Session does not exist409 Conflict— Session is not inActivestate
Endpoint: PATCH /api/sessions/{sessionId}/resume
Description: Resume a suspended session. Only sessions with Suspended status can be resumed.
Parameters:
sessionId(string, path) — Session ID
Request:
PATCH /api/sessions/session-abc123/resume
X-Api-Key: your-api-keyResponse: 200 OK — Returns the updated session.
Error Responses:
404 Not Found— Session does not exist409 Conflict— Session is not inSuspendedstate
Endpoint: GET /health
Description: Basic health check (no authentication required).
Response: 200 OK
{
"status": "ok"
}Endpoint: GET /api/config/validate
Description: Validates the platform configuration file and returns any errors.
Query Parameters:
path(string, optional) — Explicit path to a config file. Defaults to~/.botnexus/config.json.
Request:
GET /api/config/validate
X-Api-Key: your-api-keyResponse (valid config): 200 OK
{
"isValid": true,
"configPath": "/home/user/.botnexus/config.json",
"errors": []
}Response (invalid config): 200 OK
{
"isValid": false,
"configPath": "/home/user/.botnexus/config.json",
"errors": [
"agents.assistant.provider is required (example: 'copilot').",
"agents.assistant.model is required (example: 'gpt-4.1')."
]
}Validation checks include:
gateway.listenUrlmust be a valid absolute HTTP/HTTPS URL- Each provider must have
apiKeyorbaseUrl - Each agent must have
providerandmodel - Each channel must have a
type - API keys must have
apiKey,tenantId, and at least one permission
Note: The diagnostics endpoint is provided by the main BotNexus application host, not the Gateway API project.
Endpoint: GET /api/doctor
Description: Run comprehensive health diagnostics with auto-fix recommendations.
Request:
GET /api/doctor
X-Api-Key: your-api-keyResponse: 200 OK
{
"timestamp": "2026-01-15T11:50:00Z",
"checks": [
{
"name": "Configuration File",
"category": "startup",
"status": "healthy",
"message": "Config file exists and is valid"
},
{
"name": "OAuth Tokens",
"category": "authentication",
"status": "warning",
"message": "Copilot token expires in 2 days",
"suggestedFix": "Run 'botnexus login' to refresh"
}
],
"summary": {
"healthy": 11,
"warnings": 1,
"errors": 0
}
}All error responses follow a standard format:
{
"error": "unauthenticated",
"message": "API key is missing or invalid."
}Note: Controller-level errors may return a simplified format:
{
"error": "Agent not found"
}| Code | Meaning |
|---|---|
| 200 | Success |
| 201 | Created |
| 202 | Accepted (async operation queued) |
| 204 | No Content (success with no body) |
| 400 | Bad Request (invalid input) |
| 401 | Unauthorized (missing/invalid API key) |
| 404 | Not Found |
| 409 | Conflict (duplicate name, state mismatch) |
| 429 | Too Many Requests (rate limit exceeded or agent concurrency limit) |
| 500 | Internal Server Error |
| Code | HTTP | Meaning |
|---|---|---|
| INVALID_INPUT | 400 | Missing or invalid field |
| AGENT_NOT_FOUND | 404 | Agent does not exist |
| DUPLICATE_AGENT | 409 | Agent name already exists |
| SESSION_NOT_FOUND | 404 | Session does not exist |
| UNAUTHORIZED | 401 | Invalid or missing API key |
| CONCURRENCY_LIMIT | 429 | Agent concurrency limit exceeded |
| RATE_LIMITED | 429 | Per-client request rate limit exceeded |
| STATE_CONFLICT | 409 | Invalid state transition (e.g., suspend non-active session) |
| INTERNAL_ERROR | 500 | Server error |
Every agent automatically gets the following tools by default:
| Tool | Purpose | Default Status | Can Disable |
|---|---|---|---|
filesystem |
Read/write files on disk | Enabled | Yes |
web_fetch |
Fetch content from URLs | Enabled | Yes |
send_message |
Send messages via channels | Enabled | Yes |
cron |
Schedule periodic tasks | Enabled | Yes |
get_datetime |
Get current UTC and timezone-aware local date/time | Enabled | Yes |
shell |
Execute shell commands | Enabled if tools.exec.enable=true |
Yes |
To disable specific tools for an agent, add them to the disallowedTools array:
Config Example:
{
"BotNexus": {
"Agents": {
"Named": {
"secure-agent": {
"DisallowedTools": ["shell", "filesystem"]
}
}
}
}
}API Example:
curl -X POST http://localhost:5005/api/agents \
-H "X-Api-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"agentId": "secure-agent",
"displayName": "Secure Agent",
"modelId": "gpt-4.1",
"apiProvider": "copilot"
}'- Open a session in the WebUI
- Look for the "Model:" dropdown at the top
- Select a model from the dropdown (or leave it blank for default)
- Send a message — the selected model will be used for that request
If maxTokens or temperature are not specified (null), the provider uses its own defaults:
{
"name": "my-agent",
"model": "gpt-4o",
"provider": "openai",
"maxTokens": null, // Provider uses OpenAI default
"temperature": null // Provider uses OpenAI default
}Fallback Order:
- Agent-specific setting (if set)
- Default agent config (if set)
- Provider's default setting
Configuration is stored in ~/.botnexus/config.json:
{
"gateway": {
"listenUrl": "http://localhost:5005",
"defaultAgentId": "assistant"
},
"agents": {
"my-agent": {
"provider": "openai",
"model": "gpt-4.1",
"isolationStrategy": "in-process",
"enabled": true
}
},
"providers": {
"openai": {
"apiKey": "sk-...",
"baseUrl": "https://api.openai.com/v1",
"defaultModel": "gpt-4.1"
}
}
}- Automatic Backups: Every config write creates
config.json.bak - Token Logging: OAuth token operations are logged with WARNING level
- Model Logging: The actual model used (resolved from config or provider default) is logged per provider call
Example log output:
[Information] Calling provider OpenAiProvider for agent my-agent, model=gpt-4o, contextWindowTokens=128000
[Information] Provider OpenAiProvider responded in 1234ms
[Warning] OAuth token saved for provider 'copilot' at ~/.botnexus/tokens/copilot.json. Expires at 2026-02-15T10:30:00Z
Type / in the chat input to open the command palette:
/help— Show available commands and their descriptions/reset— Reset the current conversation session/status— Show system status and last heartbeat time/models— List all available models by provider (alias:/model)
Usage:
- Type
/helin the chat input - Palette shows matching commands
- Press Tab/Enter or click to select
- Command is inserted into the input
Use the 🔧 Tools toggle in the toolbar to show/hide tool calls in the chat.
- Enabled: Tool calls are shown as collapsible summaries
- Disabled: Tool calls are hidden (arguments still processed normally)
Each tool call shows:
- Tool name (e.g., "filesystem")
- Action performed (e.g., "read_file")
- Arguments preview (truncated to 80 chars)
- Click to expand full details in a modal
Agent loop detection and iteration limits are configured via config.json and the Configuration Guide — they are not exposed through REST API endpoints. This is intentional to ensure consistency and prevent accidental runtime misconfigurations.
Two settings control the agent loop behavior:
| Setting | Default | Purpose |
|---|---|---|
MaxToolIterations |
40 | Max number of LLM calls in a single agent cycle |
MaxRepeatedToolCalls |
2 | Max times the same tool can be called with identical arguments |
{
"BotNexus": {
"Agents": {
"MaxToolIterations": 40,
"MaxRepeatedToolCalls": 2,
"Named": {
"careful-agent": {
"MaxToolIterations": 10,
"MaxRepeatedToolCalls": 1
},
"researcher": {
"MaxToolIterations": 100,
"MaxRepeatedToolCalls": 5
}
}
}
}
}When an agent repeatedly calls the same tool with identical arguments:
- First call: Tool signature computed (
tool_name + normalized_arguments) - Second call: Signature compared to first; counter incremented to 2
- Threshold reached: If counter ≥
MaxRepeatedToolCalls, execution is blocked - LLM receives error: Error returned to agent:
"Tool 'X' called N times with identical arguments" - Agent recovers: Agent can now try a different tool or modify arguments
Example:
Iteration 0: search_files("") → executes (count: 1)
Iteration 1: search_files("") → executes (count: 2)
Iteration 2: search_files("") → BLOCKED (count: 2 >= MaxRepeatedToolCalls: 2)
- Most agents (10-40 iterations): Default settings work well
- Safety-critical agents: Use
MaxToolIterations=10-20, MaxRepeatedToolCalls=1 - Exploratory/research agents: Use
MaxToolIterations=50+, MaxRepeatedToolCalls=3-5
For detailed configuration guidance, see Configuration Guide § Agent Iteration & Loop Detection.
Real-time streaming connection for interacting with agents.
Connection URL:
ws://localhost:5005/ws?agent={agentId}&session={sessionId}
| Parameter | Required | Description |
|---|---|---|
agent |
Yes | Target agent ID |
session |
No | Session ID (auto-generated if omitted) |
Authentication: Subject to GatewayAuthMiddleware rules. In development mode (no API keys configured), connections are allowed without auth.
Session locking: Each session allows one active WebSocket at a time. A second connection to the same session is closed with status code 4409 ("Session already has an active connection").
Rate limiting: WebSocket connections are rate-limited per client IP + agent. Default: 20 attempts per 300-second window. Excess connections receive HTTP 429 with a Retry-After header.
Send a message:
{ "type": "message", "content": "What is 2+2?" }Abort current execution:
{ "type": "abort" }Inject steering message into active run:
{ "type": "steer", "content": "Focus on the main point." }Queue a follow-up for the next run:
{ "type": "follow_up", "content": "And what about 3+3?" }Keepalive ping:
{ "type": "ping" }Connection established:
{ "type": "connected", "connectionId": "abc123", "sessionId": "def456" }Agent started processing:
{ "type": "message_start", "messageId": "uuid-..." }Thinking delta (streaming agent reasoning):
{ "type": "thinking_delta", "delta": "Let me think about...", "messageId": "uuid-..." }Content delta (streaming response text):
{ "type": "content_delta", "delta": "2+2 is 4", "messageId": "uuid-..." }Tool execution started:
{ "type": "tool_start", "toolCallId": "call_...", "toolName": "calculate", "messageId": "uuid-..." }Tool result received:
{ "type": "tool_end", "toolCallId": "call_...", "toolName": "calculate", "toolResult": "4", "toolIsError": false, "messageId": "uuid-..." }Agent completed:
{ "type": "message_end", "messageId": "uuid-...", "usage": { "inputTokens": 50, "outputTokens": 100 } }Error occurred:
{ "type": "error", "message": "Agent not found", "code": "NOT_FOUND" }Keepalive pong:
{ "type": "pong" }Reconnect acknowledgement:
{ "type": "reconnect_ack", "sessionKey": "def456", "replayed": 3, "lastSeqId": 42 }Sequence IDs: All server → client messages include a
sequenceId(integer) for replay tracking. Clients should store the last receivedsequenceIdand pass it aslastSeqIdwhen reconnecting to avoid missing events.
Client Server
│ │
│──── { type: "message" } ─────►│
│ │
│◄──── { type: "message_start" }│
│◄──── { type: thinking_delta } │ (if thinking enabled)
│◄──── { type: content_delta } │ (streamed chunks)
│◄──── { type: content_delta } │
│◄──── { type: tool_start } │ (if tool called)
│◄──── { type: tool_end } │
│◄──── { type: content_delta } │ (post-tool response)
│◄──── { type: message_end } │
│ │
When a client disconnects unexpectedly, missed events can be replayed:
- Client stores
sequenceIdfrom the last received server message. - Client reconnects to
ws://host/ws?agent={agentId}&session={sessionId}. - Client sends:
{ "type": "reconnect", "sessionKey": "{sessionId}", "lastSeqId": {lastSequenceId} }. - Server replays all buffered events with
sequenceId > lastSeqId(up toReplayWindowSize, default 1000). - Server sends:
{ "type": "reconnect_ack", "sessionKey": "...", "replayed": N, "lastSeqId": ... }.
Client Server
│ (connection dropped) │
│ │
│──── [new WebSocket] ─────────►│
│◄──── { type: "connected" } ───│
│ │
│──── { type: "reconnect", │
│ sessionKey: "...", │
│ lastSeqId: 42 } ──────►│
│ │
│◄──── (replayed event seqId 43)│
│◄──── (replayed event seqId 44)│
│◄──── { type: "reconnect_ack" }│
│ │
Real-time stream of gateway activity events for monitoring.
Connection URL:
ws://localhost:5005/ws/activity?agent={agentFilter}
| Parameter | Required | Description |
|---|---|---|
agent |
No | Filter events to a specific agent ID. Omit for all agents. |
Behavior:
- Subscribes to
IActivityBroadcasterand streams all gateway activity events - Events are JSON-serialized with
camelCasenaming - Connection stays open until the client disconnects or the server shuts down
- When an
agentfilter is provided, only events for that agent are sent
Activity Event Schema:
{
"eventId": "a1b2c3d4e5f6",
"type": "AgentProcessing",
"agentId": "assistant",
"sessionId": "abc123",
"channelType": "signalr",
"message": "Agent started processing request",
"timestamp": "2026-01-15T10:30:00Z",
"data": { "model": "gpt-4.1" }
}Event Fields:
| Field | Type | Description |
|---|---|---|
eventId |
string | Unique event identifier |
type |
string | Activity type (see table below) |
agentId |
string? | Agent involved, if any |
sessionId |
string? | Session involved, if any |
channelType |
string? | Channel involved, if any |
message |
string? | Human-readable summary |
timestamp |
string | ISO 8601 timestamp |
data |
object? | Extensible payload data |
Activity Types:
| Type | Description |
|---|---|
MessageReceived |
A message was received from a channel |
ResponseSent |
A response was sent to a channel |
StreamDelta |
A streaming delta was sent to a channel |
AgentProcessing |
An agent started processing a request |
AgentCompleted |
An agent completed processing |
ToolExecutionStarted |
A tool execution started |
ToolExecutionCompleted |
A tool execution completed |
AgentStarted |
An agent instance was created |
AgentStopped |
An agent instance was stopped |
SessionCreated |
A session was created |
Error |
An error occurred |
System |
A system-level informational event |
- Developer Guide — Local development setup and workflow
- Configuration Guide — Detailed configuration options
- Getting Started — Quick start guide
- Extension Development — Build custom tools and providers