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.
| Method | Path | Purpose |
|---|---|---|
GET | /admin/users | List every registered user |
POST | /admin/invite | Create an invitation link |
PUT | /admin/users/:id/role | Update a user's role |
PUT | /admin/users/:id/limits | Set rate limits for a user |
DELETE | /admin/users/:id | Delete a user account |
Errors
Every error response uses the same shape:
{ "ok": false, "error": "Descriptive message", "code": "RATE_LIMIT_EXCEEDED" }
| HTTP | Meaning |
|---|---|
400 | Invalid request, bad input or missing parameters |
401 | Unauthorized, missing or invalid token |
403 | Forbidden, role lacks the required permission |
404 | Not found |
429 | Rate limited |
500 | Internal error, please open an issue |
| Code | Meaning |
|---|---|
INVALID_INPUT | Request body validation failed |
UNAUTHORIZED | No valid auth token provided |
FORBIDDEN | User lacks the required role |
NOT_FOUND | Server, tool, or document not found |
RATE_LIMIT_EXCEEDED | Window quota hit |
SERVER_DISCONNECTED | Backend MCP server is offline |
TOOL_CALL_FAILED | Backend tool returned an error |