Moxby Relay API
REST API for interacting with Moxby agents running on a user's desktop.
Overview
Moxby Relay is a cloud server that bridges third-party applications to Moxby agents. The user's desktop app maintains an outbound WebSocket tunnel to this server. Your API requests are forwarded through the tunnel to the user's local agent gateway, and responses stream back in real-time.
Your App Moxby Relay User's Desktop
──────── ─────────── ──────────────
POST /v1/chat ──HTTPS─→ [relay.moxby.com] ←─outbound WSS─ [Moxby App]
←──SSE── ──tunnel─────→ [AI Gateway]
Base URL: https://relay.moxby.com
All /v1/* endpoints require authentication. The user must have the Moxby desktop app running with the Agent API enabled.
Authentication
All API requests require a Bearer token in the Authorization header.
API keys are created in the Moxby desktop app under Settings → Agent API.
Authorization: Bearer mxby_your_api_key_here
Keys are prefixed with mxby_ and are shown only once at creation. Store them securely.
chat, agents, and sessions scopes.
Quick Start
Send a message (streaming)
curl -N -X POST https://relay.moxby.com/v1/chat \
-H "Authorization: Bearer mxby_your_key" \
-H "Content-Type: application/json" \
-d '{"message": "Hello, what can you do?"}'
Send a message (wait for full response)
curl -X POST https://relay.moxby.com/v1/chat/sync \
-H "Authorization: Bearer mxby_your_key" \
-H "Content-Type: application/json" \
-d '{"message": "Hello, what can you do?"}'
Send to a specific agent
curl -X POST https://relay.moxby.com/v1/chat/sync \
-H "Authorization: Bearer mxby_your_key" \
-H "Content-Type: application/json" \
-d '{"message": "Analyze this data", "agentId": "data-analyst"}'
List available agents
curl https://relay.moxby.com/v1/agents \
-H "Authorization: Bearer mxby_your_key"
POST /v1/chat
Send a message and receive a streaming response via Server-Sent Events (SSE).
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
message | string | Yes | The message to send |
sessionKey | string | No | Session identifier (default: "api-default") |
agentId | string | No | Target agent ID. Omit for the default assistant. |
thinking | string | No | Thinking mode: "full" or "summary" |
Response
Returns a text/event-stream with the following events:
// Connection established, run started
event: start
data: {"runId": "8d762ad7-...", "status": "started"}
// Agent streaming text
event: agent
data: {"stream": "assistant", "data": {"delta": "Hello! I can "}}
// Tool usage
event: agent
data: {"stream": "tool", "data": {"name": "web_search", ...}}
// Stream complete
event: done
data: {"status": "completed"}
See SSE Events for full event type reference.
POST /v1/chat/sync
Send a message and wait for the complete response as JSON. Simpler than SSE but blocks until the agent finishes.
Request Body
Same as POST /v1/chat.
Response
{
"message": "Hello! I'm your AI assistant. I can help you with...",
"thinking": "The user is asking about my capabilities...",
"toolCalls": [{"name": "web_search", ...}],
"runId": "8d762ad7-9610-4333-b935-042632565d23"
}
| Field | Type | Description |
|---|---|---|
message | string | The agent's full text response |
thinking | string? | Agent's reasoning (if thinking mode enabled) |
toolCalls | array? | Tools the agent used during the run |
runId | string? | Unique run identifier |
POST /v1/chat/history
Retrieve conversation history for a session. Requires sessions scope.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
sessionKey | string | No | Session to retrieve (default: "api-default") |
limit | number | No | Max messages to return |
Response
{
"messages": [
{"role": "user", "content": "Hello"},
{"role": "assistant", "content": "Hi there! How can I help?"}
]
}
POST /v1/chat/abort
Abort a running agent request. Requires chat scope.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
sessionKey | string | No | Session to abort (default: "api-default") |
runId | string | No | Specific run to abort |
Response
{"ok": true}
GET /v1/agents
List all agents available on the user's desktop. Requires agents scope.
Response
{
"defaultId": "main",
"agents": [
{"id": "main", "name": "Assistant"},
{"id": "data-analyst", "name": "Data Analyst"},
{"id": "coder", "name": "Coder"}
]
}
GET /v1/sessions
List active sessions. Requires sessions scope.
Query Parameters
| Param | Type | Description |
|---|---|---|
limit | number | Max sessions to return (default: 50) |
GET /health
Public health check endpoint. No authentication required.
Response
{
"status": "ok",
"activeTunnels": 1,
"uptime": 3600
}
SSE Events
When using POST /v1/chat, the response is a Server-Sent Event stream. Each event has a type and JSON data payload.
| Event | Description |
|---|---|
start | Run started. Payload: { runId, status } |
agent | Agent event (text delta, tool call, thinking). See below. |
chat | Chat event (final message, state change) |
done | Stream complete. No more events will follow. |
error | Error occurred. Payload: { error } |
Agent Event Streams
The agent event payload contains a stream field indicating the type:
| stream | Description |
|---|---|
assistant | Text output. Contains data.delta (incremental) or data.text (full). |
thinking | Agent reasoning. Contains data.text. |
tool | Tool invocation. Contains data.name, data.input, etc. |
Example: Reading an SSE Stream (JavaScript)
// Using EventSource or fetch
const res = await fetch('https://relay.moxby.com/v1/chat', {
method: 'POST',
headers: {
'Authorization': 'Bearer mxby_your_key',
'Content-Type': 'application/json',
},
body: JSON.stringify({ message: 'Hello' }),
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = JSON.parse(line.slice(6));
console.log(data);
}
}
}
Error Handling
Errors return a JSON body with an error field:
{"error": "message is required"}
| Status | Meaning |
|---|---|
400 | Bad request — missing or invalid parameters |
401 | Unauthorized — missing or invalid API key |
403 | Forbidden — API key lacks required scope |
429 | Rate limit exceeded — slow down |
500 | Internal server error |
502 | Agent offline — the desktop app is not connected |
502 error means the user's Moxby desktop app is not running or hasn't enabled the Agent API.
The user must open their app and enable the API in Settings → Agent API.
Rate Limiting
API requests are rate-limited per API key. Default limit: 60 requests per minute.
Rate limit info is included in response headers:
| Header | Description |
|---|---|
X-RateLimit-Limit | Max requests per window |
X-RateLimit-Remaining | Requests remaining in current window |
X-RateLimit-Reset | Unix timestamp when the window resets |
When rate-limited, you'll receive a 429 response with a retryAfterMs field:
{"error": "Rate limit exceeded", "retryAfterMs": 12000}