Skip to content

Latest commit

 

History

History
648 lines (470 loc) · 19.6 KB

File metadata and controls

648 lines (470 loc) · 19.6 KB

ClawPort -- API Reference

All API routes are Next.js App Router route handlers under app/api/. The base URL during development is http://localhost:3000.

Prerequisites

Dependency Required By Notes
OpenClaw gateway (localhost:18789) /api/chat/[id], /api/tts, /api/transcribe, /api/kanban/chat/[id] Must be running for any AI-powered route
WORKSPACE_PATH env var /api/agents, /api/memory, /api/cron-runs, /api/kanban/chat-history/[ticketId] Filesystem path to .openclaw/workspace
OPENCLAW_BIN env var /api/crons, /api/chat/[id] (vision path) Path to the openclaw CLI binary
OPENCLAW_GATEWAY_TOKEN env var All gateway-dependent routes Auth token for the OpenClaw gateway

Error Format

All error responses share a consistent JSON shape:

{ "error": "Human-readable error message" }

Returned with the appropriate HTTP status code and Content-Type: application/json.


Routes

GET /api/agents

Returns the full list of registered agents, each with their SOUL.md content loaded from the filesystem.

Data source: JSON registry file (bundled lib/agents.json or user override at $WORKSPACE_PATH/clawport/agents.json) + SOUL.md files from the workspace filesystem.

Request

No parameters.

Response

Status Content-Type Body
200 application/json Agent[]
500 application/json { "error": string }

Agent schema:

Field Type Description
id string Slug identifier (e.g. "vera")
name string Display name (e.g. "VERA")
title string Role title (e.g. "Chief Strategy Officer")
reportsTo string | null Parent agent ID, or null for the root
directReports string[] Child agent IDs
soulPath string | null Path to the agent's SOUL.md file
soul string | null Full SOUL.md content (loaded at request time), or null if file not found
voiceId string | null ElevenLabs voice ID
color string Hex color for the org chart node
emoji string Emoji identifier
tools string[] Tools this agent has access to
crons CronJob[] Always [] from this endpoint (populated client-side)
memoryPath string | null Path to the agent's memory file
description string One-liner description of the agent

Example

curl http://localhost:3000/api/agents
const res = await fetch('/api/agents')
const agents = await res.json()
// agents[0].id => "jarvis"
// agents[0].soul => "# JARVIS\n\nYou are the team's orchestrator..."

POST /api/chat/[id]

Send a chat message to an agent and receive a streaming response. This route has two pipelines depending on whether the latest user message contains images.

Requires: OpenClaw gateway running at localhost:18789.

Path Parameters

Param Type Description
id string Agent ID (must match a registered agent)

Request Body

Field Type Required Description
messages ApiMessage[] Yes Conversation history
operatorName string No Name shown to the agent as its operator. Defaults to "Operator"

ApiMessage schema:

Field Type Description
role "user" | "assistant" | "system" Message role
content string | ContentPart[] Plain text or multimodal content array

ContentPart variants:

{ type: "text", text: string }
{ type: "image_url", image_url: { url: string } }

Image URLs must be base64 data URLs (e.g. data:image/jpeg;base64,...). Client-side images should be resized to 1200px max before encoding to avoid exceeding macOS ARG_MAX.

Pipeline 1: Text Streaming

Used when the latest user message does not contain images.

The route creates a streaming chat completion via the OpenAI SDK pointed at the gateway (localhost:18789/v1/chat/completions) using model claude-sonnet-4-6.

Response: Server-Sent Events (text/event-stream).

Each SSE data line is a JSON object with a content field containing the next token:

data: {"content":"Hello"}

data: {"content":" there"}

data: [DONE]

Pipeline 2: Vision (send + poll)

Used when the latest user message does contain image_url content parts and OPENCLAW_GATEWAY_TOKEN is set.

The gateway's /v1/chat/completions endpoint strips image content, so vision messages go through the CLI agent pipeline instead:

  1. Images are extracted and converted to { mimeType, content (base64) } attachments.
  2. openclaw gateway call chat.send is invoked via execFile to send the message asynchronously.
  3. The route polls openclaw gateway call chat.history every 2 seconds (up to 60s timeout) until the assistant's response appears.
  4. The complete response is returned as a single SSE frame followed by [DONE].

Response: Same SSE format as Pipeline 1, but the entire response arrives in a single data: frame rather than streamed token-by-token.

Response Summary

Status Content-Type Body
200 text/event-stream SSE stream (both pipelines)
400 application/json { "error": string } -- invalid JSON or failed message validation
404 application/json { "error": "Agent not found" }
500 application/json { "error": "Chat failed. Make sure OpenClaw gateway is running." }

Example

// Text message
const res = await fetch('/api/chat/jarvis', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    operatorName: 'John',
    messages: [
      { role: 'user', content: 'What cron jobs are running today?' }
    ]
  })
})

const reader = res.body.getReader()
const decoder = new TextDecoder()
while (true) {
  const { done, value } = await reader.read()
  if (done) break
  const text = decoder.decode(value)
  // Parse SSE lines: "data: {\"content\":\"...\"}\n\n"
}
// Vision message (image)
const res = await fetch('/api/chat/vera', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    messages: [
      {
        role: 'user',
        content: [
          { type: 'text', text: 'What do you see in this screenshot?' },
          { type: 'image_url', image_url: { url: 'data:image/jpeg;base64,/9j/4AAQ...' } }
        ]
      }
    ]
  })
})

GET /api/crons

Returns all cron jobs registered with OpenClaw, enriched with schedule descriptions, agent ownership, and delivery config.

Data source: Runs openclaw cron list --json via the CLI (OPENCLAW_BIN required).

Request

No parameters.

Response

Status Content-Type Body
200 application/json CronJob[]
500 application/json { "error": string }

CronJob schema:

Field Type Description
id string Job identifier
name string Job name (used to match owning agent by prefix)
schedule string Raw cron expression
scheduleDescription string Human-readable (e.g. "Daily at 8 AM")
timezone string | null Timezone from schedule object, if present
status "ok" | "error" | "idle" Last run outcome
lastRun string | null ISO 8601 timestamp of last execution
nextRun string | null ISO 8601 timestamp of next scheduled run
lastError string | null Error message from last failed run
agentId string | null Owning agent ID (matched by job name prefix)
description string | null Job description
enabled boolean Whether the job is active
delivery CronDelivery | null Delivery config (mode, channel, to)
lastDurationMs number | null Duration of last run in milliseconds
consecutiveErrors number Count of consecutive failed runs
lastDeliveryStatus string | null Delivery outcome of last run

CronDelivery schema:

Field Type Description
mode string Delivery mode
channel string Delivery channel
to string | null Delivery recipient

Example

curl http://localhost:3000/api/crons

GET /api/cron-runs

Returns cron run history parsed from JSONL log files on the filesystem. Results are sorted newest-first.

Data source: Reads .jsonl files from $WORKSPACE_PATH/../cron/runs/.

Query Parameters

Param Type Required Description
jobId string No Filter to runs for a specific job. When provided, reads only {jobId}.jsonl. When omitted, reads all .jsonl files in the runs directory.

Response

Status Content-Type Body
200 application/json CronRun[]
500 application/json { "error": string }

CronRun schema:

Field Type Description
ts number Unix timestamp (milliseconds) of the run
jobId string Job identifier
status "ok" | "error" Run outcome
summary string | null Summary of what the run produced
error string | null Error message if the run failed
durationMs number Duration in milliseconds
deliveryStatus string | null Delivery outcome

Example

# All runs
curl http://localhost:3000/api/cron-runs

# Runs for a specific job
curl "http://localhost:3000/api/cron-runs?jobId=pulse-daily-digest"

GET /api/memory

Returns the contents of key memory files from the workspace: long-term memory, team memory, team intel, and the daily logs for today and yesterday.

Data source: Reads specific files from the $WORKSPACE_PATH filesystem directory.

Files checked (in order):

  1. $WORKSPACE_PATH/MEMORY.md -- Long-Term Memory (Jarvis)
  2. $WORKSPACE_PATH/memory/team-memory.md -- Team Memory
  3. $WORKSPACE_PATH/memory/team-intel.json -- Team Intel (JSON)
  4. $WORKSPACE_PATH/memory/{YYYY-MM-DD}.md -- Daily Log (Today)
  5. $WORKSPACE_PATH/memory/{YYYY-MM-DD}.md -- Daily Log (Yesterday)

Only files that exist are included in the response.

Request

No parameters.

Response

Status Content-Type Body
200 application/json MemoryFile[]
500 application/json { "error": string }

MemoryFile schema:

Field Type Description
label string Human-readable label (e.g. "Long-Term Memory (Jarvis)")
path string Absolute filesystem path to the file
content string Full file contents
lastModified string ISO 8601 timestamp of last modification

Example

curl http://localhost:3000/api/memory
const res = await fetch('/api/memory')
const files = await res.json()
// files[0].label => "Long-Term Memory (Jarvis)"
// files[0].content => "# Memory\n\n..."

POST /api/tts

Converts text to speech audio using the OpenClaw gateway's TTS endpoint (OpenAI-compatible audio.speech API).

Requires: OpenClaw gateway running at localhost:18789.

Request Body

Field Type Required Description
text string Yes The text to synthesize
voice string No Voice identifier. Defaults to "alloy"

Response

Status Content-Type Body
200 audio/mpeg Raw MP3 audio bytes
400 application/json { "error": "Missing or invalid \"text\" field" }
500 application/json { "error": "TTS failed. Make sure OpenClaw gateway is running." }

The Content-Length header is set on successful responses.

Example

curl -X POST http://localhost:3000/api/tts \
  -H 'Content-Type: application/json' \
  -d '{"text": "Hello from Jarvis", "voice": "alloy"}' \
  --output speech.mp3
const res = await fetch('/api/tts', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ text: 'Hello from Jarvis', voice: 'nova' })
})
const audioBlob = await res.blob()
const audioUrl = URL.createObjectURL(audioBlob)

POST /api/transcribe

Transcribes audio to text using the OpenClaw gateway's Whisper endpoint (OpenAI-compatible audio.transcriptions API).

Requires: OpenClaw gateway running at localhost:18789.

Request Body

Multipart form data (multipart/form-data).

Field Type Required Description
audio File Yes Audio file (webm, mp4, wav, etc.)

Response

Status Content-Type Body
200 application/json { "text": string }
400 application/json { "error": "Expected multipart form data" } or { "error": "Missing audio file" }
500 application/json { "error": "Transcription failed. Check OpenClaw gateway." }

Example

curl -X POST http://localhost:3000/api/transcribe \
  -F 'audio=@recording.webm'
const formData = new FormData()
formData.append('audio', audioBlob, 'recording.webm')

const res = await fetch('/api/transcribe', { method: 'POST', body: formData })
const { text } = await res.json()
// text => "Hello, what are the latest metrics?"

POST /api/kanban/chat/[id]

Send a chat message to an agent in the context of a kanban ticket. Similar to the main chat route but includes ticket context in the system prompt. Text-only (no vision pipeline).

Requires: OpenClaw gateway running at localhost:18789.

Path Parameters

Param Type Description
id string Agent ID (must match a registered agent)

Request Body

Field Type Required Description
messages KanbanMessage[] Yes Conversation history
ticket Ticket No Ticket context to include in the system prompt

KanbanMessage schema:

Field Type Description
role "user" | "assistant" Message role
content string Message text

Ticket schema:

Field Type Description
title string Ticket title
description string Ticket description
status string Current status
priority string Priority level
assigneeRole string | null Role of the assigned agent
workResult string | null Previous work output (included in prompt so the agent can reference it)

Response

Status Content-Type Body
200 text/event-stream SSE stream (same format as /api/chat/[id])
400 application/json { "error": string } -- invalid JSON or messages not an array
404 application/json { "error": "Agent not found" }
500 application/json { "error": "Chat failed. Make sure OpenClaw gateway is running." }

SSE format is identical to the main chat route's text pipeline:

data: {"content":"I see this ticket is about..."}

data: {"content":" the daily digest."}

data: [DONE]

Example

const res = await fetch('/api/kanban/chat/pulse', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    messages: [
      { role: 'user', content: 'What is the status of this ticket?' }
    ],
    ticket: {
      title: 'Fix daily digest formatting',
      description: 'The email digest has broken HTML in the header.',
      status: 'in-progress',
      priority: 'high',
      assigneeRole: 'pulse',
      workResult: null
    }
  })
})

GET /api/kanban/chat-history/[ticketId]

Retrieve the persisted chat history for a kanban ticket.

Data source: Reads from $WORKSPACE_PATH/../kanban/chats/{ticketId}.jsonl on the filesystem.

Path Parameters

Param Type Description
ticketId string Ticket identifier

Response

Status Content-Type Body
200 application/json StoredChatMessage[] (sorted oldest-first)
500 application/json { "error": string }

Returns an empty array [] if no chat history file exists for the ticket.

StoredChatMessage schema:

Field Type Description
id string Unique message identifier
role "user" | "assistant" Message role
content string Message text
timestamp number Unix timestamp (milliseconds)

Example

curl http://localhost:3000/api/kanban/chat-history/ticket-abc-123

POST /api/kanban/chat-history/[ticketId]

Append chat messages to the persisted history for a kanban ticket. Creates the chats directory and JSONL file if they do not exist.

Data source: Appends to $WORKSPACE_PATH/../kanban/chats/{ticketId}.jsonl on the filesystem.

Path Parameters

Param Type Description
ticketId string Ticket identifier

Request Body

Field Type Required Description
messages StoredChatMessage[] Yes Messages to append (must be a non-empty array)

See StoredChatMessage schema in the GET endpoint above.

Response

Status Content-Type Body
200 application/json { "ok": true }
400 application/json { "error": "messages array required" }
500 application/json { "error": string }

Example

await fetch('/api/kanban/chat-history/ticket-abc-123', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    messages: [
      { id: 'msg-1', role: 'user', content: 'Can you look into this?', timestamp: 1709400000000 },
      { id: 'msg-2', role: 'assistant', content: 'On it.', timestamp: 1709400005000 }
    ]
  })
})

Route Summary

Method Endpoint Gateway Required Data Source Content-Type
GET /api/agents No Filesystem (JSON + SOUL.md) application/json
POST /api/chat/[id] Yes Gateway (streaming) or CLI (vision) text/event-stream
GET /api/crons No CLI (openclaw cron list) application/json
GET /api/cron-runs No Filesystem (JSONL) application/json
GET /api/memory No Filesystem (Markdown/JSON) application/json
POST /api/tts Yes Gateway (audio.speech) audio/mpeg
POST /api/transcribe Yes Gateway (audio.transcriptions) application/json
POST /api/kanban/chat/[id] Yes Gateway (streaming) text/event-stream
GET /api/kanban/chat-history/[ticketId] No Filesystem (JSONL) application/json
POST /api/kanban/chat-history/[ticketId] No Filesystem (JSONL) application/json

SSE Stream Protocol

All streaming chat endpoints (/api/chat/[id] and /api/kanban/chat/[id]) use the same Server-Sent Events protocol:

  1. Each data frame is a JSON object: data: {"content":"token text"}\n\n
  2. The stream terminates with: data: [DONE]\n\n
  3. Content-Type is text/event-stream with Cache-Control: no-cache and Connection: keep-alive.
  4. If a stream error occurs mid-response, the server sends [DONE] and closes the connection (no error frame is sent).

Client-side consumption pattern

async function readStream(response) {
  const reader = response.body.getReader()
  const decoder = new TextDecoder()
  let fullText = ''

  while (true) {
    const { done, value } = await reader.read()
    if (done) break

    const chunk = decoder.decode(value, { stream: true })
    const lines = chunk.split('\n')

    for (const line of lines) {
      if (line.startsWith('data: ')) {
        const payload = line.slice(6)
        if (payload === '[DONE]') return fullText
        try {
          const { content } = JSON.parse(payload)
          fullText += content
        } catch { /* skip malformed frames */ }
      }
    }
  }

  return fullText
}