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.

API keys are scoped. By default keys have 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

FieldTypeRequiredDescription
messagestringYesThe message to send
sessionKeystringNoSession identifier (default: "api-default")
agentIdstringNoTarget agent ID. Omit for the default assistant.
thinkingstringNoThinking 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"
}
FieldTypeDescription
messagestringThe agent's full text response
thinkingstring?Agent's reasoning (if thinking mode enabled)
toolCallsarray?Tools the agent used during the run
runIdstring?Unique run identifier

POST /v1/chat/history

Retrieve conversation history for a session. Requires sessions scope.

Request Body

FieldTypeRequiredDescription
sessionKeystringNoSession to retrieve (default: "api-default")
limitnumberNoMax 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

FieldTypeRequiredDescription
sessionKeystringNoSession to abort (default: "api-default")
runIdstringNoSpecific 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

ParamTypeDescription
limitnumberMax 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.

EventDescription
startRun started. Payload: { runId, status }
agentAgent event (text delta, tool call, thinking). See below.
chatChat event (final message, state change)
doneStream complete. No more events will follow.
errorError occurred. Payload: { error }

Agent Event Streams

The agent event payload contains a stream field indicating the type:

streamDescription
assistantText output. Contains data.delta (incremental) or data.text (full).
thinkingAgent reasoning. Contains data.text.
toolTool 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"}
StatusMeaning
400Bad request — missing or invalid parameters
401Unauthorized — missing or invalid API key
403Forbidden — API key lacks required scope
429Rate limit exceeded — slow down
500Internal server error
502Agent offline — the desktop app is not connected
A 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:

HeaderDescription
X-RateLimit-LimitMax requests per window
X-RateLimit-RemainingRequests remaining in current window
X-RateLimit-ResetUnix timestamp when the window resets

When rate-limited, you'll receive a 429 response with a retryAfterMs field:

{"error": "Rate limit exceeded", "retryAfterMs": 12000}