Skip to content
This repository was archived by the owner on Apr 7, 2026. It is now read-only.

Latest commit

 

History

History
850 lines (666 loc) · 11.8 KB

File metadata and controls

850 lines (666 loc) · 11.8 KB

WOPR HTTP API

The WOPR daemon exposes an HTTP API for programmatic access. Default port: 7437.

Base URL: http://localhost:7437

Authentication

Currently, the API is designed for local access. For remote deployments, use a reverse proxy with authentication (nginx, etc.).

Content Types

All endpoints accept and return application/json unless otherwise specified.

Sessions

List Sessions

GET /sessions

Response:

{
  "sessions": [
    {
      "name": "mybot",
      "id": "sess_abc123",
      "context": "You are a helpful assistant...",
      "created": 1705000000000
    }
  ]
}

Get Session

GET /sessions/:name

Response:

{
  "name": "mybot",
  "id": "sess_abc123",
  "context": "You are a helpful assistant..."
}

Create Session

POST /sessions
Content-Type: application/json

{
  "name": "mybot",
  "context": "You are a helpful assistant."
}

Response:

{
  "name": "mybot",
  "context": "You are a helpful assistant.",
  "created": true
}

Delete Session

DELETE /sessions/:name

Response:

{
  "deleted": true
}

Get Conversation History

GET /sessions/:name/conversation?limit=50

Response:

{
  "name": "mybot",
  "entries": [
    {
      "ts": 1705000000000,
      "from": "user",
      "content": "Hello!",
      "type": "message"
    },
    {
      "ts": 1705000001000,
      "from": "WOPR",
      "content": "Hello! How can I help you today?",
      "type": "response"
    }
  ],
  "count": 2
}

Inject Message (Streaming)

POST /sessions/:name/inject
Content-Type: application/json

{
  "message": "Hello!",
  "from": "api",
  "silent": false
}

Response: SSE stream

event: chunk
data: {"type":"text","content":"Hello"}

event: chunk
data: {"type":"text","content":"!"}

event: done
data: {"type":"complete","response":"Hello! How can I help?","sessionId":"sess_abc123","cost":0.0023}

Stream events:

  • chunk - Text chunk from AI
  • tool_use - Tool execution started
  • done - Complete response with metadata
  • error - Error occurred

Log Message (No AI Response)

POST /sessions/:name/log
Content-Type: application/json

{
  "message": "Context information",
  "from": "system"
}

Response:

{
  "logged": true
}

Plugins

List Plugins

GET /plugins

Response:

{
  "plugins": [
    {
      "name": "wopr-plugin-discord",
      "version": "1.0.0",
      "description": "Discord integration",
      "source": "github",
      "enabled": true,
      "installedAt": 1705000000000
    }
  ]
}

Install Plugin

POST /plugins
Content-Type: application/json

{
  "source": "github:wopr-network/wopr-plugin-discord"
}

Response:

{
  "installed": true,
  "plugin": {
    "name": "wopr-plugin-discord",
    "version": "1.0.0",
    "description": "Discord integration",
    "source": "github",
    "enabled": true
  }
}

Source formats:

  • github:user/repo - GitHub repository
  • npm:package-name - npm package
  • /path/to/plugin - Local path

Remove Plugin

DELETE /plugins/:name

Response:

{
  "removed": true
}

Enable/Disable Plugin

POST /plugins/:name/enable
POST /plugins/:name/disable

Response:

{
  "enabled": true,
  "name": "wopr-plugin-discord"
}

Get Web UI Extensions

GET /plugins/ui

Response:

{
  "extensions": [
    {
      "id": "discord-nav",
      "label": "Discord",
      "href": "/discord",
      "icon": "message-circle"
    }
  ]
}

Get UI Components

GET /plugins/components

Response:

{
  "components": [
    {
      "id": "discord-panel",
      "type": "panel",
      "component": "DiscordPanel",
      "props": {}
    }
  ]
}

Identity

Note: Identity, Peers, Access Control, and Discovery endpoints require the wopr-plugin-p2p plugin.

Get Identity

GET /identity

Response:

{
  "publicKey": "abc123...",
  "shortId": "MCoxK8f2",
  "encryptPub": "xyz789..."
}

Rotate Keys

POST /identity/rotate
Content-Type: application/json

{
  "broadcast": true
}

Response:

{
  "rotated": true,
  "newShortId": "MCoxK8f2",
  "broadcast": true
}

Peers

List Peers

GET /peers

Response:

{
  "peers": [
    {
      "id": "ABC123...",
      "shortId": "ABC123",
      "name": "Alice",
      "sessions": ["help", "dev"],
      "caps": ["inject"]
    }
  ]
}

Get Peer

GET /peers/:id

Add Peer

POST /peers
Content-Type: application/json

{
  "publicKey": "abc123...",
  "encryptPub": "xyz789...",
  "name": "Alice"
}

Remove Peer

DELETE /peers/:id

Access Control

List Access Grants

GET /access

Response:

{
  "grants": [
    {
      "id": "grant_abc123",
      "peerKey": "abc123...",
      "peerEncryptPub": "xyz789...",
      "sessions": ["help"],
      "caps": ["inject"],
      "created": 1705000000000
    }
  ]
}

Create Invite

POST /access/invites
Content-Type: application/json

{
  "peerPublicKey": "abc123...",
  "sessions": ["help"],
  "caps": ["inject"]
}

Response:

{
  "token": "wop1://eyJ2IjoxLC...",
  "expires": 1705600000000
}

Claim Invite

POST /access/claims
Content-Type: application/json

{
  "token": "wop1://eyJ2IjoxLC..."
}

Response:

{
  "claimed": true,
  "peer": {
    "id": "ABC123...",
    "shortId": "ABC123",
    "sessions": ["help"]
  }
}

Revoke Access

DELETE /access/:grantId

Cron Jobs

List Crons

GET /crons

Response:

{
  "crons": [
    {
      "name": "morning",
      "schedule": "0 9 * * *",
      "session": "daily",
      "message": "Good morning!",
      "enabled": true
    }
  ]
}

Add Cron

POST /crons
Content-Type: application/json

{
  "name": "morning",
  "schedule": "0 9 * * *",
  "session": "daily",
  "message": "Good morning! What's the plan?"
}

Schedule formats:

  • Cron: 0 9 * * * (daily at 9am)
  • Natural: @daily, @hourly
  • Relative: +1h, +30m

Remove Cron

DELETE /crons/:name

Run Cron Now

POST /crons/:name/run

Skills

List Skills

GET /skills

Response:

{
  "skills": [
    {
      "name": "code-review",
      "description": "Code review skill",
      "source": "github:anthropics/claude-skills"
    }
  ]
}

Install Skill

POST /skills
Content-Type: application/json

{
  "source": "github:anthropics/claude-skills/code-review"
}

Remove Skill

DELETE /skills/:name

Configuration

Get Config

GET /config
GET /config/:key

Response:

{
  "key": "plugins.data.discord",
  "value": {
    "botToken": "...",
    "channelId": "..."
  }
}

Set Config

PUT /config/:key
Content-Type: application/json

{
  "value": { "botToken": "..." }
}

Delete Config Key

DELETE /config/:key

Discovery

Join Topic

POST /discover/topics
Content-Type: application/json

{
  "topic": "ai-agents"
}

Leave Topic

DELETE /discover/topics/:topic

List Topics

GET /discover/topics

Set Profile

PUT /discover/profile
Content-Type: application/json

{
  "name": "Alice",
  "skills": ["coding", "review"],
  "description": "AI coding assistant"
}

List Discovered Peers

GET /discover/peers

Response:

{
  "peers": [
    {
      "id": "ABC123...",
      "shortId": "ABC123",
      "profile": {
        "name": "Bob",
        "skills": ["design"]
      },
      "topics": ["ai-agents"]
    }
  ]
}

Connect to Peer

POST /discover/connect
Content-Type: application/json

{
  "peerId": "ABC123..."
}

Providers

Built-in providers: anthropic (Claude), codex (OpenAI Codex). Additional providers available via plugins.

List Providers

GET /providers

Response:

{
  "providers": [
    {
      "id": "anthropic",
      "name": "Anthropic Claude",
      "available": true,
      "defaultModel": "claude-sonnet-4-20250514"
    },
    {
      "id": "codex",
      "name": "OpenAI Codex",
      "available": false,
      "defaultModel": "codex"
    }
  ]
}

Get Provider

GET /providers/:id

Set Session Provider

PUT /sessions/:name/provider
Content-Type: application/json

{
  "provider": "kimi",
  "model": "kimi-k2"
}

Middleware

List Middlewares

GET /middlewares

Response:

{
  "middlewares": [
    {
      "name": "filter",
      "priority": 100,
      "enabled": true
    }
  ]
}

Get Middleware Chain

GET /middlewares/chain

Response:

{
  "chain": [
    { "name": "filter", "priority": 100, "enabled": true },
    { "name": "transform", "priority": 50, "enabled": true }
  ]
}

Error Responses

All errors follow this format:

{
  "error": "Description of what went wrong",
  "code": "ERROR_CODE",
  "details": {}
}

Common status codes:

  • 400 - Bad Request (invalid input)
  • 404 - Not Found
  • 409 - Conflict (e.g., session already exists)
  • 500 - Internal Server Error

WebSocket API

Some endpoints support WebSocket for real-time updates:

const ws = new WebSocket('ws://localhost:7437/ws');

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log(data.type, data.payload);
};

Events:

  • session:injection - Message injected to session
  • session:stream - Streaming response chunk
  • peer:connected - Peer connected
  • peer:disconnected - Peer disconnected

Rate Limiting

Default rate limits (configurable):

  • 100 requests per minute per IP
  • 10 concurrent streaming connections

Reverse Proxy Deployments

When WOPR runs behind a reverse proxy (nginx, Caddy, etc.), rate limiting is applied per client IP. Set the TRUSTED_PROXY environment variable to your proxy's IP address so WOPR can read the real client IP from X-Forwarded-For:

TRUSTED_PROXY=172.18.0.2

Multiple trusted proxies (comma-separated):

TRUSTED_PROXY=172.18.0.2,10.0.0.1

Only valid IPv4 and IPv6 addresses are accepted. WOPR walks the X-Forwarded-For header right-to-left, skipping trusted proxy entries, to identify the real client IP. Without TRUSTED_PROXY set, X-Forwarded-For is ignored and the socket address is used directly.

SDK Examples

JavaScript/TypeScript

const WOPR_API = 'http://localhost:7437';

async function injectMessage(session: string, message: string) {
  const response = await fetch(`${WOPR_API}/sessions/${session}/inject`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ message, from: 'api' }),
  });
  
  // Handle SSE stream
  const reader = response.body?.getReader();
  while (reader) {
    const { done, value } = await reader.read();
    if (done) break;
    // Process chunk
  }
}

Python

import requests

WOPR_API = 'http://localhost:7437'

def create_session(name: str, context: str):
    resp = requests.post(f'{WOPR_API}/sessions', json={
        'name': name,
        'context': context
    })
    return resp.json()

def list_sessions():
    resp = requests.get(f'{WOPR_API}/sessions')
    return resp.json()['sessions']

cURL

# Create session
curl -X POST http://localhost:7437/sessions \
  -H "Content-Type: application/json" \
  -d '{"name":"mybot","context":"You are helpful"}'

# Inject message (streaming)
curl -X POST http://localhost:7437/sessions/mybot/inject \
  -H "Content-Type: application/json" \
  -d '{"message":"Hello!","from":"curl"}'

# List plugins
curl http://localhost:7437/plugins