Master API Reference¶
This document collects the HTTP API exposed by the CapOwn Master.
The Master is the coordination point in a CapOwn network. It authenticates clients and workers, tracks worker liveness and ownership, routes tasks over worker SSE connections, receives task results, and manages user/token state.
This file describes the REST API implemented by master/api/*.py and the MCP
adapter mounted by master/mcp/. The CLI commands in capown-master may
provide higher-level workflows that are not exposed as HTTP endpoints yet.
Base URL¶
Examples use the direct Master URL:
Deployments behind a reverse proxy commonly expose the same routes under a public URL such as:
All JSON request bodies use Content-Type: application/json. Bearer-token
authentication uses:
Auth Modes¶
| Mode | Used by | Credential |
|---|---|---|
| Public | GET /health |
None |
| Bundle enrollment | GET /api/enroll/{token} |
Token embedded in URL plus X-CapOwn-Enrollment-Role |
| Worker enrollment | POST /api/workers/register |
enrollment_token in JSON body |
| Worker session | Worker heartbeat, SSE, result reporting | Session token from Ed25519 challenge-response |
| Client token | Worker listing and task dispatch | client auth token |
| MCP client | All /mcp operations, including initialization and tool discovery |
client auth token |
| Admin token | /api/admin/* |
admin auth token |
Current admin API authentication is based on token_type == "admin". Planned
dashboard identity changes should update this section when they land.
Error Shape¶
CapOwn-layer errors are JSON:
Common error_code values are defined in shared/protocol.py, including:
auth_denied
schema_invalid
worker_offline
timeout
user_not_found
token_revoked
enrollment_invalid
enrollment_expired
enrollment_exhausted
enrollment_role_not_allowed
enrollment_revoked
missing_role_header
signature_invalid
Endpoint Summary¶
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | /health |
Public | Master health and connected worker IDs |
| GET, POST | /mcp |
Client token | MCP Streamable HTTP transport |
| GET | /api/enroll/{token} |
Bundle enrollment URL | Return role-specific enrollment TOML |
| POST | /api/workers/register |
Enrollment token in body | Register a worker and bind ownership |
| POST | /api/workers/auth/challenge |
Public worker identity | Issue an Ed25519 nonce |
| POST | /api/workers/auth/verify |
Signed nonce | Issue a worker session token |
| POST | /api/workers/{worker_id}/reconnect |
Worker session token | Report updated metadata after restart |
| POST | /api/workers/heartbeat |
Worker session token | Refresh worker liveness |
| GET | /api/workers |
Client token | List workers owned by the client user |
| POST | /api/workers/{worker_id}/rename |
Client token or worker session | Rename an owned worker |
| GET | /api/events?worker_id=... |
Worker session token | Open worker SSE stream |
| POST | /api/tasks/dispatch |
Client token | Dispatch a task asynchronously |
| POST | /api/tasks/dispatch_sync |
Client token | Dispatch and wait up to soft timeout |
| POST | /api/tasks/result |
Worker session token | Report task result |
| GET | /api/tasks/{task_id} |
Client token | Deprecated status endpoint |
| GET | /api/admin/health |
Admin token | Check admin API access |
| POST | /api/admin/users |
Admin token | Create a user |
| GET | /api/admin/users/{username} |
Admin token | Get user details |
| POST | /api/admin/users/{username}/tokens |
Admin token | Create a client or admin auth token |
| GET | /api/admin/users/{username}/tokens |
Admin token | List auth tokens for a user |
| POST | /api/admin/workers/{worker_id}/rename |
Admin token | Admin rename a worker |
| POST | /api/admin/tokens/{token_id}/revoke |
Admin token | Revoke an auth token |
Health¶
GET /health¶
Returns basic Master health and the worker IDs currently connected to the in-memory broker.
Response:
mcp_enabled reports whether the MCP adapter initialized successfully. It
does not report client authentication or Worker connectivity.
MCP API¶
GET, POST /mcp¶
Exposes CapOwn Worker, shell, file, system, and task operations through the MCP Streamable HTTP transport. MCP uses the same client bearer tokens and Worker ownership rules as the REST client API. Admin tokens and Worker session tokens are rejected.
Authentication is checked before MCP initialization, tool discovery, and tool
calls. Requests that include an Origin header must match the configured
Master public_url by scheme, host, and port. A supplied Origin is rejected if
public_url is unset, malformed, or does not match. Non-browser MCP clients
may omit the Origin header.
See MCP Support for the tool reference, client configuration, request headers, error behavior, and reverse proxy configuration.
Enrollment API¶
GET /api/enroll/{token}¶
Redeems a temporary bundle enrollment URL and returns role-specific TOML. The same URL can be used separately by a client and a worker when the bundle token allows both roles.
Required header:
or:
Success response:
Client TOML response:
# SPDX-License-Identifier: Apache-2.0
role = "client"
master_url = "https://master.example.com"
client_token = "..."
[client]
soft_timeout = 30
Worker TOML response:
# SPDX-License-Identifier: Apache-2.0
role = "worker"
master_url = "https://master.example.com"
enrollment_token = "..."
[worker]
execution_mode = "host"
workspace = "/"
max_runtime = 86400
idle_timeout = 600
reconnect_interval = 5
Notes:
- The token in the URL is the credential.
- The role is supplied by header, not by URL path.
- Bundle role counters are claimed atomically.
- Worker TOML receives a newly issued one-shot worker enrollment token.
- Client TOML receives a newly issued client auth token.
Worker API¶
POST /api/workers/register¶
Registers a worker using a one-shot worker enrollment token. The token is consumed only after registration succeeds.
Request:
{
"enrollment_token": "cown_enroll_xxx",
"worker_name": "worker-1",
"public_key": "hex-ed25519-public-key",
"hostname": "host-a",
"os": "linux",
"mode": "container",
"capabilities": ["shell.run", "file.read"],
"workspace": "/workspace"
}
Response 201:
Important behavior:
- The Master stores the worker public key for later challenge-response auth.
- The worker is bound to the enrollment token owner's user ID.
- A duplicate
worker_namereturns409and does not consume the enrollment token.
POST /api/workers/auth/challenge¶
Starts worker session authentication by issuing a nonce for a registered worker.
Request:
Response:
POST /api/workers/auth/verify¶
Verifies the worker signature and returns a session token. The session token is used for heartbeat, SSE, and result reporting.
Request:
Response:
Notes:
- The nonce is single-use and expires after 5 minutes.
- The signature is verified against the registered Ed25519 public key.
- Worker session tokens are in-memory and expire after 24 hours.
POST /api/workers/heartbeat¶
Updates liveness for a worker.
Auth: worker session token.
Request:
Response:
The session token is bound to one worker ID. A mismatch returns 403.
POST /api/workers/{worker_id}/reconnect¶
Updates runtime metadata (hostname, OS, execution mode, capabilities, workspace) for a worker after a restart or reconnection. Identity fields (worker_id, worker_name, public_key, ownership, registration time) are preserved.
Auth: worker session token.
Request:
{
"hostname": "new-host",
"os": "windows",
"mode": "host",
"capabilities": ["shell.run", "file.read"],
"workspace": "/new/workspace"
}
Response:
{
"worker_id": "wrk_a1b2c3d4e5f6",
"worker_name": "worker-1",
"hostname": "new-host",
"os": "windows",
"mode": "host",
"capabilities": ["shell.run", "file.read"],
"workspace": "/new/workspace",
"status": "online",
"last_heartbeat": "2026-07-10T12:00:00",
"registered_at": "2026-07-08T00:00:00",
"previous_worker_name": null,
"renamed_at": null
}
Notes:
- The session token must belong to the worker in the URL path.
- Only the request fields are updated;
worker_id,worker_name,public_key, ownership, andregistered_atare never changed. - A revoked or unknown worker returns
404witherror_code: "worker_offline". - The Worker calls this endpoint automatically after each authentication cycle.
GET /api/events?worker_id=abc123¶
Opens the Server-Sent Events stream used by Master to deliver tasks to a worker.
Auth: worker session token.
SSE event examples:
Notes:
- The request must include
worker_id. - The Master marks the worker online when the stream is connected.
- The Master emits ping events on idle intervals.
- Disconnect marks the worker offline in the registry.
Client API¶
GET /api/workers¶
Lists workers owned by the authenticated client user.
Auth: client token.
Response:
[
{
"worker_id": "abc123",
"worker_name": "worker-1",
"hostname": "host-a",
"os": "linux",
"mode": "container",
"capabilities": ["shell.run", "file.read"],
"workspace": "/workspace",
"status": "online",
"last_heartbeat": "2026-07-08T00:00:00",
"registered_at": "2026-07-08T00:00:00",
"previous_worker_name": null,
"renamed_at": null
}
]
Only workers owned by the token's user are returned.
Workers are sorted by registered_at ascending (oldest first), with workers
missing a registration timestamp placed last. Equal timestamps are resolved by
worker_id ascending.
POST /api/workers/{worker_id}/rename¶
Renames an owned worker. The worker_id is preserved and routing continues
to the same physical node. The URL path must use the canonical worker_id
(not worker_name).
Auth: client token (owner check) or worker session token.
Request:
Response:
{
"worker_id": "wrk_a1b2c3d4e5f6",
"worker_name": "new-worker-name",
"hostname": "host-a",
"os": "linux",
"mode": "container",
"capabilities": ["shell.run", "file.read"],
"workspace": "/workspace",
"status": "online",
"last_heartbeat": "2026-07-08T00:00:00",
"registered_at": "2026-07-08T00:00:00",
"previous_worker_name": "old-worker-name",
"renamed_at": "2026-07-09T12:00:00"
}
Validation:
worker_namemust be 3-48 ASCII characters (lowercase letters, digits, dots, hyphens, underscores), starting and ending with alphanumeric.- The name must not be reserved (
wrk_*prefix, common reserved words). - Duplicate names per owner return
409.
Task API¶
Task dispatch requests use the shared DispatchRequest model. The
target_worker field is always a canonical worker_id - the client CLI
resolves worker_name to worker_id before sending, but the API itself
does not accept worker names for routing.
{
"target_worker": "abc123",
"payload": {
"task_type": "shell",
"params": {
"command": "uname -a"
}
},
"timeout": 30
}
Supported task_type values are defined in shared/protocol.py:
shell
file_read
file_write
list_dir
system_info
task_status
task_cancel
task_list
file_edit
file_find
file_search
task_command
The Master checks that the authenticated client user owns target_worker.
POST /api/tasks/dispatch¶
Dispatches a task asynchronously over the worker SSE connection.
Auth: client token.
Response:
The Master does not persist task history. Store both target_worker and
task_id; worker task IDs are worker-local for follow-up operations.
POST /api/tasks/dispatch_sync¶
Dispatches a task and waits up to the request timeout for a result.
Auth: client token.
Completed response:
{
"task_id": "abc123def456",
"worker_id": "abc123",
"status": "completed",
"output": "hello\n",
"error": "",
"error_code": null,
"truncated": false,
"completed_at": "2026-07-08T00:00:00",
"started_at": "2026-07-08T00:00:00",
"updated_at": "2026-07-08T00:00:00",
"output_size": 6,
"error_size": 0,
"preview": "",
"hint": "",
"next_actions": []
}
If a shell task is still running after the soft timeout, the Master queries the worker for status and may return:
{
"task_id": "abc123def456",
"worker_id": "abc123",
"status": "running",
"output": "partial output",
"preview": "partial output",
"output_size": 1024,
"error_size": 0,
"hint": "Task is still running. To check progress later: capown task abc123 abc123def456. To wait longer for the final result: capown wait abc123 abc123def456",
"next_actions": ["check_later", "wait_longer", "cancel"]
}
For non-shell tasks that do not finish in time, the response is a 504
timeout error.
POST /api/tasks/result¶
Receives the result of a task from a worker.
Auth: worker session token.
Request:
{
"task_id": "abc123def456",
"worker_id": "abc123",
"status": "completed",
"output": "hello\n",
"error": "",
"error_code": null,
"truncated": false,
"output_size": 6,
"error_size": 0
}
Response:
The session token must be bound to the reporting worker ID, and the worker must be owned by the session user.
GET /api/tasks/{task_id}¶
Deprecated. This endpoint always returns a schema error because task status requires an explicit target worker.
Response 400:
{
"error": "task status requires explicit target_worker; use dispatch_sync task_status",
"error_code": "schema_invalid"
}
Use a task_status task through POST /api/tasks/dispatch_sync instead.
Admin API¶
Admin endpoints currently require an admin auth token. Client tokens and worker session tokens are rejected.
GET /api/admin/health¶
Checks admin API authentication.
Response:
POST /api/admin/users¶
Creates a user.
Request:
Response 201:
Current API accepts role values user and admin.
GET /api/admin/users/{username}¶
Gets a user by username.
Response:
POST /api/admin/users/{username}/tokens¶
Creates an auth token for a user.
Request:
token_type may be client or admin.
Response 201:
{
"token": "plaintext-token-shown-once",
"token_id": "abc123def456",
"token_type": "client",
"token_prefix": "abcdef",
"created_at": "2026-07-08T00:00:00"
}
Notes:
- Plaintext token values are returned only at creation time.
- Admin token creation rotates prior active admin tokens for that user.
- Bundle enrollment URLs are currently created by
capown-master tokens createrather than this HTTP endpoint.
GET /api/admin/users/{username}/tokens¶
Lists auth tokens for a user.
Response:
[
{
"token_id": "abc123def456",
"token_type": "client",
"token_prefix": "abcdef",
"name": "laptop",
"created_at": "2026-07-08T00:00:00",
"last_used_at": null,
"revoked_at": null
}
]
POST /api/admin/tokens/{token_id}/revoke¶
Revokes an auth token.
Response:
Only auth_tokens are handled here. Enrollment token revocation is currently a
local Master CLI operation.
POST /api/admin/workers/{worker_id}/rename¶
Admin endpoint to rename a worker. Does not require worker ownership - the admin token grants full access.
Auth: admin token.
Request:
Same response shape and validation rules as POST /api/workers/{worker_id}/rename.
State Ownership¶
The Master uses persistent SQLite-backed state for:
- Users.
- Auth token hashes.
- Enrollment token hashes and usage counters.
- Worker registry records.
- Worker ownership.
The Master uses in-memory state for:
- Connected worker SSE queues.
- Pending task result waiters.
- Worker challenge nonces.
- Worker session tokens.
The Master does not persist task history or command history. Worker task state and shell command history live on each Worker.
Security Notes¶
- Do not expose direct HTTP Master access without TLS in production.
- Treat enrollment URLs, client tokens, admin tokens, and worker session tokens as secrets.
- The URL token for
/api/enroll/{token}can create credentials, so it must not be logged or pasted into public channels. - Client task routes enforce worker ownership before dispatch.
- MCP requests require a client token and use the same ownership-aware routing as REST requests.
- Browser-originated MCP requests require an exact scheme, host, and port match
against the configured
public_url. - Worker routes enforce session binding to the authenticated worker ID.
- Admin routes currently trust admin auth tokens. Future dashboard work should update this model if user-role-based temporary access tokens replace admin tokens.