OpalServe

HTTP API

Every REST endpoint at /api/v1, request and response shapes, auth and error contracts.

OpalServe exposes a REST API at http://127.0.0.1:3456/api/v1 by default. In team-server mode every endpoint except /health requires authentication via JWT or API key.

Authentication

Include one of these headers on every request:

Authorization: Bearer <jwt-or-api-key>

JWTs are obtained from POST /api/v1/auth/login. API keys are issued by POST /api/v1/auth/api-keys. API keys are HMAC-SHA256 hashed at rest; the raw key is returned exactly once at creation.

POST /auth/login

{ "email": "alice@company.com", "password": "..." }

Response: { ok, data: { token, user, expiresAt } }.

POST /auth/logout

Invalidates the current JWT.

GET /auth/me

Returns the authenticated user, id, email, role, team, key count, createdAt.

POST /auth/api-keys

{ "name": "CI/CD Pipeline", "expiresIn": 2592000 }

The key field in the response is shown once. Store it securely.

DELETE /auth/api-keys/:id

Revokes an API key. Returns 204.

Health

GET /health

Public. No auth. Used for liveness probes.

{
  "ok": true,
  "data": {
    "status": "running",
    "version": "3.4.0",
    "mode": "team-server",
    "uptime": 3600,
    "servers": { "total": 4, "connected": 4 },
    "tools": { "total": 29 },
    "users": { "total": 8, "active": 5 }
  }
}

Servers

GET /servers

Lists every registered MCP server with status, transport type, tool count, tags.

POST /servers

Registers a new server. Admin role required.

{
  "name": "my-files",
  "description": "Shared filesystem",
  "transport": {
    "type": "stdio",
    "command": "npx",
    "args": ["-y", "@modelcontextprotocol/server-filesystem", "/opt/shared"]
  },
  "tags": ["files"]
}

Returns 201 Created.

DELETE /servers/:name

Removes a server and its tool index. Admin role required.

POST /servers/:name/reconnect

Disconnects and reconnects to a server. Useful after upstream changes. Admin role.

GET /servers/:name/health

Per-server liveness, connection status, latency, tool count, lastSeen.

Tools

GET /tools

Every indexed tool. Query params: server (filter by name, comma-separated), tags (filter by server tags).

GET /tools/search?q=<query>

Full-text search. limit defaults to 20. server filter optional.

GET /tools/:id

Single tool. The id is serverName:toolName, URL-encode the colon as %3A.

POST /tools/:id/call

Proxies a tool call to its backend MCP server.

curl -X POST "http://localhost:3456/api/v1/tools/github%3Acreate_issue/call" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "owner": "my-org", "repo": "my-repo", "title": "..." }'

Response wraps the backend MCP result under data. Errors are normalized.

Drift

POST /drift

Compares the calling client's local registry against the team server's team_servers table. See Drift Detection for the full state machine.

{ "client": "alice@laptop" }

Response includes stats (counts by state) and entries (per-server detail with differentFields).

Stats

GET /stats?period=24h

Admin role required. Period is 1h, 24h, 7d, or 30d (default 24h).

{
  "period": "24h",
  "totalRequests": 847,
  "toolCalls": 312,
  "activeUsers": 5,
  "uniqueTools": 15,
  "topTools": [{ "id": "github:create_issue", "calls": 47 }],
  "topUsers": [{ "email": "alice@company.com", "requests": 234 }],
  "errorRate": 0.02
}

GET /stats/users

Per-user breakdown. Admin role required.

Personal

GET /me/activity

The authenticated user's recent tool calls. Backed by the usage_events table.

Audit

GET /audit

Audit log entries, auth events, server changes, admin actions. Admin role required. Supports limit, offset, actor, action query params.

Webhooks

POST /webhooks/github

GitHub webhook receiver. Validates X-Hub-Signature-256 against the configured webhookSecret. No bearer auth, the signature is the auth.

POST /slack/commands and POST /slack/events

Slack receivers. Validated via the Slack signing secret.

Admin

The /admin/* namespace requires the admin role.

MethodPathPurpose
GET/admin/usersList every registered user
POST/admin/inviteCreate an invitation link
PUT/admin/users/:id/roleUpdate a user's role
PUT/admin/users/:id/limitsSet rate limits for a user
DELETE/admin/users/:idDelete a user account

Errors

Every error response uses the same shape:

{ "ok": false, "error": "Descriptive message", "code": "RATE_LIMIT_EXCEEDED" }
HTTPMeaning
400Invalid request, bad input or missing parameters
401Unauthorized, missing or invalid token
403Forbidden, role lacks the required permission
404Not found
429Rate limited
500Internal error, please open an issue
CodeMeaning
INVALID_INPUTRequest body validation failed
UNAUTHORIZEDNo valid auth token provided
FORBIDDENUser lacks the required role
NOT_FOUNDServer, tool, or document not found
RATE_LIMIT_EXCEEDEDWindow quota hit
SERVER_DISCONNECTEDBackend MCP server is offline
TOOL_CALL_FAILEDBackend tool returned an error