Skip to content

HTTP API

OpalServe exposes a REST API at http://127.0.0.1:3456/api/v1 by default. In team-server mode, all endpoints (except health) require authentication via JWT token or API key.

Authentication

In team-server mode, include one of these headers with every request:

bash
# JWT token (obtained from login)
Authorization: Bearer eyJhbGciOiJIUzI1NiI...

# API key
Authorization: Bearer osk_abc123...

POST /api/v1/auth/login

Authenticate and receive a JWT token.

Request:

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

Response:

json
{
  "ok": true,
  "data": {
    "token": "eyJhbGciOiJIUzI1NiI...",
    "user": {
      "id": "usr_a1b2c3",
      "email": "alice@company.com",
      "role": "developer",
      "team": "Acme Engineering"
    },
    "expiresAt": "2026-04-13T00:00:00.000Z"
  }
}

POST /api/v1/auth/logout

Invalidate the current JWT token.

Response:

json
{
  "ok": true,
  "data": { "message": "Logged out" }
}

GET /api/v1/auth/me

Get the current authenticated user.

Response:

json
{
  "ok": true,
  "data": {
    "id": "usr_a1b2c3",
    "email": "alice@company.com",
    "role": "developer",
    "team": "Acme Engineering",
    "apiKeys": 2,
    "createdAt": "2026-04-01T00:00:00.000Z"
  }
}

POST /api/v1/auth/api-keys

Create a new API key for the authenticated user.

Request:

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

Response:

json
{
  "ok": true,
  "data": {
    "id": "key_d4e5f6",
    "name": "CI/CD Pipeline",
    "key": "osk_abc123...",
    "expiresAt": "2026-05-12T00:00:00.000Z"
  }
}

WARNING

The key field is only returned once at creation time. Store it securely.

DELETE /api/v1/auth/api-keys/:id

Revoke an API key.

Response: 204 No Content


Health

GET /api/v1/health

Server health and statistics. Does not require authentication.

Response:

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

Servers

GET /api/v1/servers

List all registered MCP servers.

Response:

json
{
  "ok": true,
  "data": [
    {
      "name": "github",
      "description": "GitHub repositories and issues",
      "status": "connected",
      "transport": "stdio",
      "toolCount": 8,
      "tags": ["code", "github"],
      "lastSeen": "2026-04-12T10:30:00.000Z",
      "error": null,
      "serverInfo": { "name": "github-mcp-server", "version": "0.6.2" }
    }
  ]
}

POST /api/v1/servers

Register and connect a new MCP server. Requires admin role.

Request:

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

Response: 201 Created

json
{
  "ok": true,
  "data": {
    "name": "my-files",
    "status": "connected",
    "toolCount": 12
  }
}

DELETE /api/v1/servers/:name

Remove a server and its indexed tools. Requires admin role.

Response: 204 No Content

POST /api/v1/servers/:name/reconnect

Disconnect and reconnect to a specific server. Requires admin role.

Response:

json
{
  "ok": true,
  "data": {
    "name": "github",
    "status": "connected",
    "toolCount": 8
  }
}

GET /api/v1/servers/:name/health

Health check for a specific server.

Response:

json
{
  "ok": true,
  "data": {
    "name": "github",
    "status": "connected",
    "latency": 45,
    "toolCount": 8,
    "lastSeen": "2026-04-12T10:30:00.000Z"
  }
}

Tools

GET /api/v1/tools

List all discovered tools.

Query params:

ParamTypeDescription
serverstringFilter by server name(s), comma-separated
tagsstringFilter by server tags, comma-separated

Response:

json
{
  "ok": true,
  "data": [
    {
      "id": "github:create_issue",
      "serverName": "github",
      "name": "create_issue",
      "description": "Create a new issue in a repository",
      "inputSchema": {
        "type": "object",
        "properties": {
          "owner": { "type": "string" },
          "repo": { "type": "string" },
          "title": { "type": "string" },
          "body": { "type": "string" }
        },
        "required": ["owner", "repo", "title"]
      },
      "discoveredAt": "2026-04-12T10:00:00.000Z",
      "lastVerified": "2026-04-12T10:30:00.000Z"
    }
  ]
}

GET /api/v1/tools/search?q=<query>

Full-text search across all tools.

Query params:

ParamTypeDefaultDescription
qstringrequiredSearch query
limitnumber20Max results
serverstringFilter by server name(s)

Response:

json
{
  "ok": true,
  "data": [
    {
      "id": "github:create_issue",
      "name": "create_issue",
      "serverName": "github",
      "description": "Create a new issue in a repository",
      "score": 0.95
    }
  ]
}

GET /api/v1/tools/:id

Get a specific tool by ID. Tool IDs use the format serverName:toolName (URL-encode the colon as %3A).

POST /api/v1/tools/:id/call

Proxy a tool call to the backend MCP server.

Request: Tool arguments as a JSON object.

bash
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": "Bug: login form broken",
    "body": "Steps to reproduce..."
  }'

Response:

json
{
  "ok": true,
  "data": {
    "content": [
      { "type": "text", "text": "Created issue #42: Bug: login form broken" }
    ]
  }
}

Context / Knowledge Base

GET /api/v1/context

List all documents in the knowledge base.

Query params:

ParamTypeDescription
tagsstringFilter by tags, comma-separated
limitnumberMax results (default: 50)
offsetnumberPagination offset

Response:

json
{
  "ok": true,
  "data": [
    {
      "id": "doc_a1b2c3",
      "title": "Deployment Guide",
      "tags": ["ops", "deployment"],
      "size": 4200,
      "chunks": 8,
      "createdAt": "2026-04-10T00:00:00.000Z",
      "updatedAt": "2026-04-12T08:00:00.000Z"
    }
  ],
  "total": 12
}

POST /api/v1/context

Add a document to the knowledge base. Requires admin or developer role.

Request:

json
{
  "title": "Database Schema",
  "content": "# Users Table\n\n| Column | Type |\n|--------|------|\n| id | uuid |\n| email | text |\n...",
  "tags": ["database", "schema"]
}

Response: 201 Created

json
{
  "ok": true,
  "data": {
    "id": "doc_g7h8i9",
    "title": "Database Schema",
    "chunks": 5,
    "size": 2048
  }
}

GET /api/v1/context/search?q=<query>

Search the knowledge base.

Query params:

ParamTypeDefaultDescription
qstringrequiredSearch query
limitnumber10Max results
tagsstringFilter by tags

Response:

json
{
  "ok": true,
  "data": [
    {
      "documentId": "doc_a1b2c3",
      "title": "Deployment Guide",
      "chunk": "To deploy to production, merge your PR to main. The CI pipeline will automatically...",
      "score": 0.92,
      "tags": ["ops", "deployment"]
    }
  ]
}

DELETE /api/v1/context/:id

Remove a document from the knowledge base. Requires admin role.

Response: 204 No Content


Stats

GET /api/v1/stats

Usage statistics. Requires admin role.

Query params:

ParamTypeDefaultDescription
periodstring24hTime window: 1h, 24h, 7d, 30d

Response:

json
{
  "ok": true,
  "data": {
    "period": "24h",
    "totalRequests": 847,
    "toolCalls": 312,
    "activeUsers": 5,
    "uniqueTools": 15,
    "topTools": [
      { "id": "github:create_issue", "calls": 47 },
      { "id": "files:read_file", "calls": 38 }
    ],
    "topUsers": [
      { "email": "alice@company.com", "requests": 234 },
      { "email": "bob@company.com", "requests": 189 }
    ],
    "errorRate": 0.02
  }
}

GET /api/v1/stats/users

Per-user usage breakdown. Requires admin role.

Response:

json
{
  "ok": true,
  "data": [
    {
      "email": "alice@company.com",
      "role": "developer",
      "requests": 234,
      "toolCalls": 89,
      "lastActive": "2026-04-12T10:30:00.000Z"
    }
  ]
}

Webhooks

POST /api/v1/webhooks/github

GitHub webhook receiver. Validates the webhook signature using webhookSecret and processes events based on configuration.

Headers:

  • X-Hub-Signature-256 — HMAC signature
  • X-GitHub-Event — Event type

No manual authentication needed — the webhook secret serves as the auth mechanism.

POST /api/v1/slack/commands

Slack slash command receiver. Validates requests using the Slack signing secret.

POST /api/v1/slack/events

Slack Events API receiver for real-time message ingestion.


Admin Routes

GET /api/v1/admin/users

List all users. Requires admin role.

POST /api/v1/admin/invite

Create a user invitation. Requires admin role.

Request:

json
{
  "email": "newuser@company.com",
  "role": "developer",
  "expiresIn": 604800
}

Response:

json
{
  "ok": true,
  "data": {
    "inviteUrl": "https://opalserve.company.com/invite/abc123xyz",
    "expiresAt": "2026-04-19T00:00:00.000Z"
  }
}

PUT /api/v1/admin/users/:id/role

Update a user's role. Requires admin role.

Request:

json
{
  "role": "admin"
}

PUT /api/v1/admin/users/:id/limits

Set rate limits for a specific user. Requires admin role.

Request:

json
{
  "maxRequests": 200,
  "toolCallLimit": 100,
  "windowMs": 60000
}

DELETE /api/v1/admin/users/:id

Delete a user account. Requires admin role.


Error Responses

All errors follow a consistent format:

json
{
  "ok": false,
  "error": "Descriptive error message",
  "code": "RATE_LIMIT_EXCEEDED"
}

HTTP Status Codes

CodeDescription
400Invalid request — bad input or missing parameters
401Unauthorized — missing or invalid authentication
403Forbidden — insufficient permissions for this action
404Not found — resource does not exist
429Rate limited — too many requests
500Internal server error

Error Codes

CodeDescription
INVALID_INPUTRequest body validation failed
UNAUTHORIZEDNo valid auth token provided
FORBIDDENUser lacks required role/permission
NOT_FOUNDServer, tool, or document not found
RATE_LIMIT_EXCEEDEDUser hit their rate limit
SERVER_DISCONNECTEDMCP server is not connected
TOOL_CALL_FAILEDBackend tool returned an error

Released under the MIT License.