The WOPR daemon exposes an HTTP API for programmatic access. Default port: 7437.
Base URL: http://localhost:7437
Currently, the API is designed for local access. For remote deployments, use a reverse proxy with authentication (nginx, etc.).
All endpoints accept and return application/json unless otherwise specified.
GET /sessionsResponse:
{
"sessions": [
{
"name": "mybot",
"id": "sess_abc123",
"context": "You are a helpful assistant...",
"created": 1705000000000
}
]
}GET /sessions/:nameResponse:
{
"name": "mybot",
"id": "sess_abc123",
"context": "You are a helpful assistant..."
}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 /sessions/:nameResponse:
{
"deleted": true
}GET /sessions/:name/conversation?limit=50Response:
{
"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
}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 AItool_use- Tool execution starteddone- Complete response with metadataerror- Error occurred
POST /sessions/:name/log
Content-Type: application/json
{
"message": "Context information",
"from": "system"
}Response:
{
"logged": true
}GET /pluginsResponse:
{
"plugins": [
{
"name": "wopr-plugin-discord",
"version": "1.0.0",
"description": "Discord integration",
"source": "github",
"enabled": true,
"installedAt": 1705000000000
}
]
}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 repositorynpm:package-name- npm package/path/to/plugin- Local path
DELETE /plugins/:nameResponse:
{
"removed": true
}POST /plugins/:name/enable
POST /plugins/:name/disableResponse:
{
"enabled": true,
"name": "wopr-plugin-discord"
}GET /plugins/uiResponse:
{
"extensions": [
{
"id": "discord-nav",
"label": "Discord",
"href": "/discord",
"icon": "message-circle"
}
]
}GET /plugins/componentsResponse:
{
"components": [
{
"id": "discord-panel",
"type": "panel",
"component": "DiscordPanel",
"props": {}
}
]
}Note: Identity, Peers, Access Control, and Discovery endpoints require the
wopr-plugin-p2pplugin.
GET /identityResponse:
{
"publicKey": "abc123...",
"shortId": "MCoxK8f2",
"encryptPub": "xyz789..."
}POST /identity/rotate
Content-Type: application/json
{
"broadcast": true
}Response:
{
"rotated": true,
"newShortId": "MCoxK8f2",
"broadcast": true
}GET /peersResponse:
{
"peers": [
{
"id": "ABC123...",
"shortId": "ABC123",
"name": "Alice",
"sessions": ["help", "dev"],
"caps": ["inject"]
}
]
}GET /peers/:idPOST /peers
Content-Type: application/json
{
"publicKey": "abc123...",
"encryptPub": "xyz789...",
"name": "Alice"
}DELETE /peers/:idGET /accessResponse:
{
"grants": [
{
"id": "grant_abc123",
"peerKey": "abc123...",
"peerEncryptPub": "xyz789...",
"sessions": ["help"],
"caps": ["inject"],
"created": 1705000000000
}
]
}POST /access/invites
Content-Type: application/json
{
"peerPublicKey": "abc123...",
"sessions": ["help"],
"caps": ["inject"]
}Response:
{
"token": "wop1://eyJ2IjoxLC...",
"expires": 1705600000000
}POST /access/claims
Content-Type: application/json
{
"token": "wop1://eyJ2IjoxLC..."
}Response:
{
"claimed": true,
"peer": {
"id": "ABC123...",
"shortId": "ABC123",
"sessions": ["help"]
}
}DELETE /access/:grantIdGET /cronsResponse:
{
"crons": [
{
"name": "morning",
"schedule": "0 9 * * *",
"session": "daily",
"message": "Good morning!",
"enabled": true
}
]
}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
DELETE /crons/:namePOST /crons/:name/runGET /skillsResponse:
{
"skills": [
{
"name": "code-review",
"description": "Code review skill",
"source": "github:anthropics/claude-skills"
}
]
}POST /skills
Content-Type: application/json
{
"source": "github:anthropics/claude-skills/code-review"
}DELETE /skills/:nameGET /config
GET /config/:keyResponse:
{
"key": "plugins.data.discord",
"value": {
"botToken": "...",
"channelId": "..."
}
}PUT /config/:key
Content-Type: application/json
{
"value": { "botToken": "..." }
}DELETE /config/:keyPOST /discover/topics
Content-Type: application/json
{
"topic": "ai-agents"
}DELETE /discover/topics/:topicGET /discover/topicsPUT /discover/profile
Content-Type: application/json
{
"name": "Alice",
"skills": ["coding", "review"],
"description": "AI coding assistant"
}GET /discover/peersResponse:
{
"peers": [
{
"id": "ABC123...",
"shortId": "ABC123",
"profile": {
"name": "Bob",
"skills": ["design"]
},
"topics": ["ai-agents"]
}
]
}POST /discover/connect
Content-Type: application/json
{
"peerId": "ABC123..."
}Built-in providers: anthropic (Claude), codex (OpenAI Codex). Additional providers available via plugins.
GET /providersResponse:
{
"providers": [
{
"id": "anthropic",
"name": "Anthropic Claude",
"available": true,
"defaultModel": "claude-sonnet-4-20250514"
},
{
"id": "codex",
"name": "OpenAI Codex",
"available": false,
"defaultModel": "codex"
}
]
}GET /providers/:idPUT /sessions/:name/provider
Content-Type: application/json
{
"provider": "kimi",
"model": "kimi-k2"
}GET /middlewaresResponse:
{
"middlewares": [
{
"name": "filter",
"priority": 100,
"enabled": true
}
]
}GET /middlewares/chainResponse:
{
"chain": [
{ "name": "filter", "priority": 100, "enabled": true },
{ "name": "transform", "priority": 50, "enabled": true }
]
}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 Found409- Conflict (e.g., session already exists)500- Internal Server Error
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 sessionsession:stream- Streaming response chunkpeer:connected- Peer connectedpeer:disconnected- Peer disconnected
Default rate limits (configurable):
- 100 requests per minute per IP
- 10 concurrent streaming connections
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.
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
}
}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']# 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