API Reference

The Amurg Hub exposes a RESTful HTTP API for authentication, session management, message retrieval, file transfer, and administration. All endpoints return JSON. Real-time streaming is handled over WebSocket (see the WebSocket Protocol docs).

Base URL

All API paths are relative to the hub's configured address, e.g. https://hub.example.com.

Authentication

Authenticated endpoints require a Bearer token in the Authorization header. Tokens are obtained via the login endpoint (builtin auth) or from an external provider (Clerk).

Authorization: Bearer <jwt-token>

Admin endpoints additionally require the authenticated user to have the admin role.

Health & Readiness

GET /healthz

Liveness probe. Returns uptime.

{ "status": "ok", "uptime": "4h32m10s" }
GET /readyz

Readiness probe. Pings the database and returns ready status.

// 200 OK
{ "status": "ready" }

// 503 Service Unavailable
{ "status": "not_ready", "error": "db ping failed" }

Auth Endpoints

GET /api/auth/config

Returns the configured auth provider. No authentication required.

{ "provider": "builtin" }
POST /api/auth/login

Authenticate with username and password. Only available when provider is 'builtin'. Rate limited to 5 req/s per IP.

Request body:

{ "username": "admin", "password": "admin" }

Response:

{ "token": "eyJhbGciOiJIUzI1NiIs..." }
GET /api/me Auth

Returns the authenticated user's identity.

{ "id": "user-uuid", "username": "admin", "role": "admin" }

Endpoints

GET /api/endpoints Auth

List agent endpoints visible to the current user. When default_endpoint_access is 'none', non-admin users only see explicitly permitted endpoints.

[
  {
    "id": "ep-uuid",
    "runtime_id": "rt-uuid",
    "org_id": "default",
    "profile": "claude-code",
    "name": "Claude Code (prod)",
    "tags": "{}",
    "caps": "{"native_session_ids":false,"turn_completion":true,"resume_attach":false,"exec_model":"interactive"}",
    "security": "{"permission_mode":"strict","allowed_tools":["Bash","Read"]}",
    "online": true
  }
]

Sessions

POST /api/sessions Auth

Create a new session on an endpoint. Checks endpoint permissions and max session limits.

Request body:

{ "endpoint_id": "ep-uuid" }

Response (201 Created):

{
  "id": "session-uuid",
  "endpoint_id": "ep-uuid",
  "user_id": "user-uuid",
  "state": "active",
  "created_at": "2024-01-15T10:30:00Z",
  "updated_at": "2024-01-15T10:30:00Z"
}
GET /api/sessions Auth

List all sessions owned by the current user.

[
  {
    "id": "session-uuid",
    "endpoint_id": "ep-uuid",
    "user_id": "user-uuid",
    "state": "active",
    "created_at": "2024-01-15T10:30:00Z",
    "updated_at": "2024-01-15T10:32:00Z"
  }
]
GET /api/sessions/{sessionID}/messages Auth

Fetch message history for a session. Supports pagination via query params. Verifies session ownership (owner or admin).

Query parameters:

Param Default Description
limit 100 Max messages to return (capped at 500)
after_seq 0 Return messages with sequence number greater than this
[
  {
    "id": "msg-uuid",
    "session_id": "session-uuid",
    "seq": 1,
    "direction": "user",
    "channel": "stdin",
    "content": "Hello, agent!",
    "ts": "2024-01-15T10:30:05Z"
  },
  {
    "id": "msg-uuid-2",
    "session_id": "session-uuid",
    "seq": 2,
    "direction": "agent",
    "channel": "stdout",
    "content": "Hello! How can I help you?",
    "ts": "2024-01-15T10:30:06Z"
  }
]
POST /api/sessions/{sessionID}/close Auth

Close a session. Only the session owner can close it. Broadcasts session.closed to WebSocket subscribers.

{ "status": "closed" }

File Transfer

POST /api/sessions/{sessionID}/files Auth

Upload a file to a session. Multipart form data with a 'file' field. Max 10MB by default. The hub stores the file and forwards it to the runtime as a base64-encoded WebSocket message.

Response (201 Created):

{
  "file_id": "file-uuid",
  "name": "input.csv",
  "mime_type": "text/csv",
  "size": 1024
}
GET /api/files/{fileID} Auth

Download a file by its ID. Returns the file with appropriate Content-Type and Content-Disposition headers.

Returns the binary file content with headers:

Content-Type: text/csv
Content-Disposition: attachment; filename="output.csv"

WebSocket Endpoints

GET /ws/runtime

Runtime WebSocket connection. Authentication is handled inside the connection via the runtime.hello message. See the WebSocket Protocol docs.

The runtime sends a runtime.hello message immediately after connecting, which includes the runtime token and endpoint registrations.

GET /ws/client

Client WebSocket connection. The JWT token is sent as a query parameter or in the first message. See the WebSocket Protocol docs.

Used for real-time message streaming, session subscriptions, permission prompts, and session state updates.

Admin Endpoints

Admin Required

All endpoints in this section require the admin role.

Runtimes & Users

GET /api/runtimes Admin

List all registered runtimes with their online/offline status.

[
  {
    "id": "rt-uuid",
    "org_id": "default",
    "name": "prod-runtime",
    "online": true,
    "last_seen": "2024-01-15T10:30:00Z"
  }
]
GET /api/users Admin

List all users in the organization. Password hashes are stripped from the response.

[
  {
    "id": "user-uuid",
    "org_id": "default",
    "username": "admin",
    "role": "admin",
    "created_at": "2024-01-15T10:00:00Z"
  }
]
POST /api/users Admin

Create a new user. Only available with builtin auth provider. Username: 3-64 chars, password: 8-128 chars.

Request body:

{ "username": "alice", "password": "secure-password", "role": "user" }

Permissions

POST /api/permissions Admin

Grant a user access to an endpoint. Only relevant when default_endpoint_access is 'none'.

{ "user_id": "user-uuid", "endpoint_id": "ep-uuid" }
DELETE /api/permissions Admin

Revoke a user's access to an endpoint.

{ "user_id": "user-uuid", "endpoint_id": "ep-uuid" }
GET /api/users/{userID}/permissions Admin

List endpoint IDs a user has been granted access to.

{ "user_id": "user-uuid", "endpoint_ids": ["ep-uuid-1", "ep-uuid-2"] }

Session Administration

GET /api/admin/sessions Admin

List all sessions across all users in the organization.

Returns the same format as /api/sessions but includes sessions from all users.

POST /api/admin/sessions/{sessionID}/close Admin

Force-close any session. Logs a session.admin_close audit event.

{ "status": "closed" }

Endpoint Configuration

GET /api/admin/endpoints Admin

List all endpoints with runtime info, security config, and any hub-side config overrides.

[
  {
    "id": "ep-uuid",
    "org_id": "default",
    "runtime_id": "rt-uuid",
    "runtime_name": "prod-runtime",
    "runtime_online": true,
    "profile": "claude-code",
    "name": "Claude Code (prod)",
    "tags": {},
    "caps": { "turn_completion": true, "exec_model": "interactive" },
    "security": { "permission_mode": "strict" },
    "config_override": null
  }
]
GET /api/admin/endpoints/{endpointID}/config Admin

Get the hub-side config override for an endpoint.

{
  "endpoint_id": "ep-uuid",
  "org_id": "default",
  "security": "{}",
  "limits": "{}",
  "updated_by": "user-uuid",
  "updated_at": "2024-01-15T12:00:00Z"
}
PUT /api/admin/endpoints/{endpointID}/config Admin

Set or update a hub-side config override for an endpoint. The override is pushed to the connected runtime in real-time.

Request body:

{
  "security": {
    "permission_mode": "strict",
    "allowed_tools": ["Bash", "Read", "Write"],
    "allowed_paths": ["/home/user/project"],
    "denied_paths": ["/etc", "/root"]
  },
  "limits": {
    "max_sessions": 5,
    "session_timeout": "1h",
    "idle_timeout": "10m"
  }
}

Response:

{ "status": "saved", "pushed_to_runtime": true }

Audit Log

GET /api/admin/audit Admin

Query audit events with optional filtering. Supports pagination and prefix-match on action.

Query parameters:

Param Default Description
limit 50 Max events to return (capped at 500)
offset 0 Number of events to skip for pagination
action - Filter by action prefix (e.g. permission. matches all permission events)
session_id - Filter by exact session ID
endpoint_id - Filter by exact endpoint ID

Response:

[
  {
    "id": "audit-uuid",
    "org_id": "default",
    "action": "permission.granted",
    "user_id": "user-uuid",
    "session_id": "session-uuid",
    "endpoint_id": "ep-uuid",
    "detail": { "request_id": "req-uuid", "tool": "Bash" },
    "created_at": "2024-01-15T10:31:05Z"
  }
]

Event types:

Action Description
login.success Successful user authentication
login.failed Failed authentication attempt
message.sent User sent a message to a session
session.create New session created
session.stop Session stopped
session.idle_close Session closed due to idle timeout
runtime.connect Runtime connected to the hub
runtime.disconnect Runtime disconnected from the hub
turn.completed Agent turn completed (detail includes duration_ms, optional exit_code)
permission.requested Agent requested tool permission
permission.granted User approved tool permission
permission.denied User denied tool permission
permission.timeout Permission request timed out (auto-denied after 60s)

Structured Detail

The detail field is a json.RawMessage (structured JSON object, not a string). The endpoint_id and org_id fields are available as top-level properties on every audit event.

Examples

Login and create a session

# Login
TOKEN=$(curl -s -X POST http://localhost:8090/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"admin"}' | jq -r .token)

# List endpoints
curl -s http://localhost:8090/api/endpoints \
  -H "Authorization: Bearer $TOKEN" | jq .

# Create session
SESSION=$(curl -s -X POST http://localhost:8090/api/sessions \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"endpoint_id":"my-endpoint-id"}' | jq -r .id)

# Get messages
curl -s "http://localhost:8090/api/sessions/$SESSION/messages?limit=50" \
  -H "Authorization: Bearer $TOKEN" | jq .

Upload a file

curl -X POST "http://localhost:8090/api/sessions/$SESSION/files" \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@/path/to/input.csv"

Query audit log for permission events

curl -s "http://localhost:8090/api/admin/audit?action=permission.&limit=20" \
  -H "Authorization: Bearer $TOKEN" | jq .

Error Responses

All errors return a JSON body with a single error field:

{ "error": "invalid credentials" }
Status Meaning
400 Bad request (invalid body, validation failure)
401 Missing or invalid authentication token
403 Forbidden (insufficient role or no endpoint access)
404 Resource not found
409 Conflict (e.g. duplicate username)
429 Rate limited
500 Internal server error