Cloud OS API
Complete reference for the Quazzar Cloud OS REST API. All endpoints use the /api/ prefix and require authentication unless noted otherwise. The base URL is your Cloud OS instance address (for example, http://localhost:8080/api/).
See Authentication for details on obtaining and using tokens.
Authentication
Endpoints for user setup, login, token management, TOTP two-factor authentication, session management, and Control Panel OAuth integration.
| Method | Path | Description |
|---|---|---|
POST | /api/auth/setup | Create the initial admin account (first boot only) |
POST | /api/auth/login | Authenticate and receive JWT tokens |
POST | /api/auth/refresh | Refresh an expired access token |
GET | /api/auth/me | Get the current authenticated user |
POST | /api/auth/change-password | Change the admin password |
POST | /api/auth/totp/enable | Enable TOTP two-factor authentication |
POST | /api/auth/totp/verify | Verify a TOTP code |
GET | /api/auth/sessions | List active sessions |
DELETE | /api/auth/sessions/{id} | Revoke a specific session |
GET | /api/auth/cp-status | Check whether this node is connected to a Control Panel (used by login page to show/hide the “Sign in with Quazzar Cloud” button) |
Control Panel Session (Impersonate)
| Method | Path | Description |
|---|---|---|
GET | /api/cp/session | Validate a CP-issued impersonate token and create a local OS session |
This endpoint is called by the browser redirect from the CP “Open in node” button. The token query parameter must be a valid HMAC-signed JWT issued by the CP for this specific node. On success, a session cookie is set and the response redirects to the OS dashboard.
OAuth Callback (Sign in with Quazzar Cloud)
| Method | Path | Description |
|---|---|---|
GET | /api/oauth/callback | Handle the OAuth 2.0 authorization code callback from the CP IdP |
The OS redirects here after the user approves on the CP. The endpoint exchanges the code with the CP, validates the identity assertion, and creates a local OS session.
Query parameters: code (required), state (required, must match the value generated at authorization start).
Apps
Manage applications installed on the Cloud OS instance. Apps are deployed from templates as Docker containers and can be started, stopped, restarted, and monitored.
| Method | Path | Description |
|---|---|---|
GET | /api/apps | List all installed apps |
POST | /api/apps | Install a new app from a template |
GET | /api/apps/{appId} | Get app details |
DELETE | /api/apps/{appId} | Uninstall an app |
POST | /api/apps/{appId}/start | Start a stopped app |
POST | /api/apps/{appId}/stop | Stop a running app |
POST | /api/apps/{appId}/restart | Restart an app |
GET | /api/apps/{appId}/logs | Get app container logs |
GET | /api/apps/{appId}/stats | Get app resource usage statistics |
Example: Install an App
curl -X POST http://localhost:8080/api/apps \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"template": "nextcloud",
"name": "my-nextcloud",
"config": {
"admin_user": "admin",
"admin_password": "SecurePassword123"
}
}'{
"id": "app_abc123",
"name": "my-nextcloud",
"template": "nextcloud",
"status": "installing",
"created_at": "2025-01-15T10:30:00Z"
}Example: Get App Logs
curl "http://localhost:8080/api/apps/app_abc123/logs?lines=100&follow=false" \
-H "Authorization: Bearer <token>"Example: Get App Stats
curl http://localhost:8080/api/apps/app_abc123/stats \
-H "Authorization: Bearer <token>"{
"app_id": "app_abc123",
"cpu_percent": 2.5,
"memory_usage_mb": 256,
"memory_limit_mb": 1024,
"network_rx_bytes": 1048576,
"network_tx_bytes": 524288,
"disk_usage_mb": 512
}Templates
Browse the application template catalog. Templates define how apps are installed and configured.
| Method | Path | Description |
|---|---|---|
GET | /api/templates | List all available templates |
GET | /api/templates/categories | List template categories |
GET | /api/templates/{name} | Get details for a specific template |
Example: List Templates
curl http://localhost:8080/api/templates \
-H "Authorization: Bearer <token>"[
{
"name": "nextcloud",
"display_name": "Nextcloud",
"description": "Self-hosted productivity platform",
"category": "productivity",
"version": "28.0",
"icon": "nextcloud.svg"
},
{
"name": "gitea",
"display_name": "Gitea",
"description": "Lightweight self-hosted Git service",
"category": "development",
"version": "1.21",
"icon": "gitea.svg"
}
]Dashboard Layout API
Per-user, per-kind dashboard layout stored and versioned server-side. Two independent layouts exist: server (Server Home) and personal (Personal Home). Changes are written with optimistic concurrency — a stale write returns 409 Conflict and the client must refetch before retrying.
See Per-User Layout Sync for the UI-level description.
| Method | Path | Description |
|---|---|---|
GET | /api/profile/dashboard?type=server|personal | Fetch the current layout for the given dashboard kind |
PUT | /api/profile/dashboard?type=server|personal | Save a new layout. Body: { layout, version } |
GET /api/profile/dashboard
Query parameters
| Param | Required | Values |
|---|---|---|
type | yes | server or personal |
curl "http://localhost:8080/api/profile/dashboard?type=server" \
-H "Authorization: Bearer <token>"{
"type": "server",
"version": 7,
"layout": [
{ "id": "cpu", "x": 0, "y": 0, "w": 1, "h": 1 },
{ "id": "ram", "x": 1, "y": 0, "w": 1, "h": 1 },
{ "id": "network", "x": 2, "y": 0, "w": 2, "h": 2 }
]
}version is a monotonically increasing integer. Always pass the version you received back when writing.
PUT /api/profile/dashboard
Query parameters
| Param | Required | Values |
|---|---|---|
type | yes | server or personal |
Request body
{
"version": 7,
"layout": [
{ "id": "cpu", "x": 0, "y": 0, "w": 2, "h": 1 },
{ "id": "ram", "x": 2, "y": 0, "w": 2, "h": 1 },
{ "id": "network", "x": 0, "y": 1, "w": 4, "h": 2 }
]
}curl -X PUT "http://localhost:8080/api/profile/dashboard?type=server" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{ "version": 7, "layout": [...] }'Response (success)
{
"type": "server",
"version": 8
}Status codes
| Status | Meaning |
|---|---|
200 | Layout saved; response includes the new version |
400 | Missing or invalid type parameter, or malformed body |
401 | Not authenticated |
403 | Insufficient permissions |
409 | Stale version — another device has written a newer layout. Fetch the latest and retry |
When a 409 is returned the body includes the current server version so the client knows what to fetch:
{
"error": "stale_version",
"current_version": 9,
"message": "Layout was updated from another device. Reload and retry."
}System
Monitor system resources, hardware health, and performance metrics for the Cloud OS instance.
| Method | Path | Description |
|---|---|---|
GET | /api/system/metrics/current | Get current system resource usage (CPU, memory, disk) |
GET | /api/system/metrics | Get historical system metrics |
GET | /api/system/info | Get system information (OS, kernel, uptime) |
GET | /api/system/apps/metrics | Get aggregated metrics for all apps |
GET | /api/system/disk/health | Get disk health status (S.M.A.R.T.) |
GET | /api/system/disks | List real filesystems (ext4, xfs, btrfs, zfs, f2fs, vfat) |
GET | /api/system/network/interfaces | List network interfaces |
Example: Get Current Metrics
curl http://localhost:8080/api/system/metrics/current \
-H "Authorization: Bearer <token>"{
"cpu": {
"usage_percent": 12.5,
"cores": 4,
"model": "Intel Xeon E-2236"
},
"memory": {
"total_mb": 16384,
"used_mb": 8192,
"usage_percent": 50.0
},
"disk": {
"total_gb": 500,
"used_gb": 120,
"usage_percent": 24.0
},
"uptime_seconds": 864000
}Example: Get System Info
curl http://localhost:8080/api/system/info \
-H "Authorization: Bearer <token>"{
"hostname": "cloud-os-01",
"os": "Ubuntu 22.04 LTS",
"kernel": "5.15.0-91-generic",
"architecture": "x86_64",
"quazzar_version": "0.9.7",
"uptime_seconds": 864000,
"node_id": "node_abc123"
}Example: List Disks
Returns every real filesystem on the host. Pseudo-filesystems (tmpfs, devtmpfs, squashfs, overlay mounts) are excluded. The Storage Overview dashboard widget reads this endpoint.
curl http://localhost:8080/api/system/disks \
-H "Authorization: Bearer <token>"[
{
"device": "/dev/sda1",
"mount_point": "/",
"fs_type": "ext4",
"total_bytes": 214748364800,
"used_bytes": 52428800000,
"free_bytes": 162319564800,
"usage_percent": 24.4
},
{
"device": "/dev/sda2",
"mount_point": "/boot/efi",
"fs_type": "vfat",
"total_bytes": 536870912,
"used_bytes": 5242880,
"free_bytes": 531628032,
"usage_percent": 1.0
}
]Status codes
| Status | Meaning |
|---|---|
200 | List of filesystems |
401 | Not authenticated |
Network History
Per-interface throughput history used by the Network — Per Interface dashboard widget.
| Method | Path | Description |
|---|---|---|
GET | /api/network/history | Get bucketed RX/TX history for a network interface |
GET /api/network/history
Query parameters
| Param | Required | Values | Default |
|---|---|---|---|
interface | yes | Interface name, e.g. eth0 | — |
range | no | 5m, 15m, 1h, 6h, 24h | 1h |
Each range value maps to a fixed bucket size:
| Range | Bucket |
|---|---|
5m | 10 s |
15m | 30 s |
1h | 1 min |
6h | 5 min |
24h | 15 min |
curl "http://localhost:8080/api/network/history?interface=eth0&range=1h" \
-H "Authorization: Bearer <token>"{
"interface": "eth0",
"range": "1h",
"bucket_seconds": 60,
"points": [
{ "ts": "2026-05-25T10:00:00Z", "rx_bytes_per_sec": 125000, "tx_bytes_per_sec": 48000 },
{ "ts": "2026-05-25T10:01:00Z", "rx_bytes_per_sec": 132000, "tx_bytes_per_sec": 51000 }
]
}ts values are bucket start times in UTC ISO 8601. The array is sorted ascending by ts. Points with no observed traffic report 0 for both fields rather than being omitted.
Status codes
| Status | Meaning |
|---|---|
200 | History array |
400 | Missing interface param or invalid range value |
401 | Not authenticated |
Backups
Create and manage backups of app data and system configuration. Supports local and remote backup targets.
| Method | Path | Description |
|---|---|---|
GET | /api/backups | List all backups |
POST | /api/backups | Create a new backup |
POST | /api/backups/{id}/restore | Restore from a backup |
GET | /api/backup/targets | List configured backup targets |
POST | /api/backup/targets | Add a backup target (local, S3, etc.) |
GET | /api/backup/schedule | Get the backup schedule |
Example: Create a Backup
curl -X POST http://localhost:8080/api/backups \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"target": "s3-primary",
"apps": ["app_abc123", "app_def456"],
"include_config": true
}'{
"id": "bak_abc123",
"status": "in_progress",
"target": "s3-primary",
"apps": ["app_abc123", "app_def456"],
"include_config": true,
"started_at": "2025-01-15T10:30:00Z"
}Example: Configure an S3 Backup Target
curl -X POST http://localhost:8080/api/backup/targets \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"name": "s3-primary",
"type": "s3",
"config": {
"bucket": "my-backups",
"region": "us-east-1",
"access_key": "AKIAIOSFODNN7EXAMPLE",
"secret_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"encryption": true
}
}'Domains
Manage custom domains and SSL certificates for apps running on the Cloud OS instance.
| Method | Path | Description |
|---|---|---|
GET | /api/domains | List all configured domains |
POST | /api/domains | Add a new domain |
PUT | /api/domains/{id} | Update a domain configuration |
DELETE | /api/domains/{id} | Remove a domain |
GET | /api/domains/{id}/ssl | Get SSL certificate status for a domain |
Example: Add a Domain
curl -X POST http://localhost:8080/api/domains \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"domain": "cloud.example.com",
"app_id": "app_abc123",
"auto_ssl": true
}'{
"id": "dom_abc123",
"domain": "cloud.example.com",
"app_id": "app_abc123",
"ssl": {
"status": "provisioning",
"provider": "letsencrypt"
},
"created_at": "2025-01-15T10:30:00Z"
}SSL certificates are automatically provisioned through the built-in Caddy reverse proxy when auto_ssl is enabled. Ensure your domain’s DNS A record points to the Cloud OS instance before adding it.
Reverse Proxy — Domains & Routes
Manage routing rules for the embedded Caddy reverse proxy. Routes map hostnames (subdomains or full domains) to installed apps, the Cloud OS UI, or arbitrary host:port upstreams. Changes are staged and applied atomically via POST /api/proxy/apply.
See Domains & Routes Manager for the UI walkthrough, including port-conflict resolution and SSO gating.
Routes
| Method | Path | Description |
|---|---|---|
GET | /api/proxy/routes | List all configured routes |
POST | /api/proxy/routes | Create a new route |
GET | /api/proxy/routes/{id} | Get a single route |
PUT | /api/proxy/routes/{id} | Update a route. Append ?confirm=true when re-pointing the apex domain |
DELETE | /api/proxy/routes/{id} | Delete a custom route (returns 409 for apex or auto-created app routes) |
POST | /api/proxy/apply | Apply all staged route changes to Caddy |
GET | /api/proxy/upstreams | List all known upstreams (apps, OS UI, manual entries) |
Settings (admin)
| Method | Path | Description |
|---|---|---|
POST | /api/settings/proxy/takeover | Stop and disable the conflicting web server so Caddy can own ports 80/443 |
GET | /api/settings/proxy/coexist-snippet | Generate a nginx or Apache front-proxy snippet. Query param: proxy=nginx|apache |
Forward Auth
| Method | Path | Description |
|---|---|---|
GET | /api/auth/validate | Caddy forward_auth target. Returns 200 with user headers on a valid session, 401 otherwise |
Example: Create a Subdomain Route
curl -X POST http://localhost:8080/api/proxy/routes \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"hostname": "git.example.com",
"upstream_type": "app",
"upstream_id": "app_abc123",
"require_auth": true
}'{
"id": "route_abc123",
"hostname": "git.example.com",
"upstream_type": "app",
"upstream_id": "app_abc123",
"require_auth": true,
"status": "pending",
"created_at": "2025-06-01T10:00:00Z"
}status is pending until POST /api/proxy/apply is called.
Example: Create a Manual Upstream Route
curl -X POST http://localhost:8080/api/proxy/routes \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"hostname": "metrics.example.com",
"upstream_type": "manual",
"upstream_addr": "localhost:9090",
"require_auth": false
}'Example: Re-point the Apex Domain (requires confirm)
curl -X PUT "http://localhost:8080/api/proxy/routes/route_apex?confirm=true" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"upstream_type": "app",
"upstream_id": "app_abc123"
}'Omitting ?confirm=true returns 409 Conflict:
{
"error": "apex_repoint_requires_confirm",
"message": "Re-pointing the apex domain will move the OS UI away from this address. Resubmit with ?confirm=true to proceed."
}Example: Apply Routes
curl -X POST http://localhost:8080/api/proxy/apply \
-H "Authorization: Bearer <token>"{ "applied": 3, "unchanged": 7, "errors": [] }Example: List Upstreams
curl http://localhost:8080/api/proxy/upstreams \
-H "Authorization: Bearer <token>"[
{ "id": "app_abc123", "type": "app", "name": "gitea", "addr": "127.0.0.1:3000" },
{ "id": "quazzar-ui", "type": "ui", "name": "OS UI", "addr": "127.0.0.1:8080" }
]Example: Get Coexist Snippet
curl "http://localhost:8080/api/settings/proxy/coexist-snippet?proxy=nginx" \
-H "Authorization: Bearer <token>"# Paste inside your existing nginx http {} block
server {
listen 80;
listen 443 ssl;
server_name _;
# Forward everything to Quazzar on the alternate port
location / {
proxy_pass http://127.0.0.1:8443;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}Status Codes
| Status | Meaning |
|---|---|
200 | OK |
201 | Route created |
204 | Route deleted |
400 | Invalid request body or hostname |
401 | Not authenticated |
403 | Insufficient permissions (admin-only endpoints) |
409 | Apex re-point without ?confirm=true, or attempt to delete a protected route |
VPN
Manage the built-in WireGuard VPN server for secure remote access to the Cloud OS instance and its apps.
| Method | Path | Description |
|---|---|---|
GET | /api/vpn/provider | Get VPN server configuration |
POST | /api/vpn/provider | Configure the VPN server |
GET | /api/vpn/peers | List VPN peers (clients) |
POST | /api/vpn/peers | Create a new VPN peer |
DELETE | /api/vpn/peers/{id} | Remove a VPN peer |
GET | /api/vpn/peers/{id}/qrcode | Get a QR code for peer configuration |
Example: Create a VPN Peer
curl -X POST http://localhost:8080/api/vpn/peers \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"name": "my-laptop",
"dns": "1.1.1.1"
}'{
"id": "peer_abc123",
"name": "my-laptop",
"public_key": "aB1cD2eF3gH4...",
"allowed_ips": "10.0.0.2/32",
"config": "[Interface]\nPrivateKey = ...\nAddress = 10.0.0.2/32\nDNS = 1.1.1.1\n\n[Peer]\n..."
}The config field contains the full WireGuard configuration file content for the peer. You can also retrieve a QR code for easy mobile setup:
curl http://localhost:8080/api/vpn/peers/peer_abc123/qrcode \
-H "Authorization: Bearer <token>"Alerts
Configure alert rules and notification channels to monitor app and system health.
| Method | Path | Description |
|---|---|---|
GET | /api/alerts/rules | List alert rules |
POST | /api/alerts/rules | Create an alert rule |
GET | /api/alerts/channels | List notification channels |
POST | /api/alerts/channels | Create a notification channel |
POST | /api/alerts/channels/{id}/test | Send a test notification |
Example: Create an Alert Rule
curl -X POST http://localhost:8080/api/alerts/rules \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"name": "High CPU Usage",
"metric": "system.cpu.usage_percent",
"condition": "greater_than",
"threshold": 90,
"duration": "5m",
"channel_id": "ch_abc123",
"severity": "warning"
}'Example: Create a Notification Channel
curl -X POST http://localhost:8080/api/alerts/channels \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"name": "Ops Team Slack",
"type": "slack",
"config": {
"webhook_url": "https://hooks.slack.com/services/T00/B00/xxxx"
}
}'Security
Run security scans, view findings, and manage Web Application Firewall (WAF) rules.
| Method | Path | Description |
|---|---|---|
GET | /api/security/score | Get the overall security score |
POST | /api/security/scan | Trigger a security scan |
GET | /api/security/findings | List security findings |
GET | /api/waf/rules | List WAF rules |
Example: Get Security Score
curl http://localhost:8080/api/security/score \
-H "Authorization: Bearer <token>"{
"overall_score": 85,
"categories": {
"tls": 95,
"headers": 80,
"firewall": 90,
"authentication": 75,
"updates": 85
},
"findings_count": {
"critical": 0,
"high": 1,
"medium": 3,
"low": 5
}
}Example: Trigger a Security Scan
curl -X POST http://localhost:8080/api/security/scan \
-H "Authorization: Bearer <token>"{
"scan_id": "scan_abc123",
"status": "running",
"started_at": "2025-01-15T10:30:00Z"
}API Keys
Manage API keys for programmatic access to the Cloud OS instance.
| Method | Path | Description |
|---|---|---|
GET | /api/keys | List all API keys |
POST | /api/keys | Create a new API key |
DELETE | /api/keys/{id} | Revoke an API key |
See the Authentication page for detailed examples of creating and using API keys.
Webhooks
Configure outgoing webhooks to receive notifications when events occur on the Cloud OS instance.
| Method | Path | Description |
|---|---|---|
GET | /api/webhooks | List configured webhooks |
POST | /api/webhooks | Create a new webhook |
PUT | /api/webhooks/{id} | Update a webhook |
DELETE | /api/webhooks/{id} | Delete a webhook |
See Webhooks for detailed webhook setup, event types, and signature verification.
AI
Interact with locally hosted AI models for chat and RAG (Retrieval-Augmented Generation) search.
| Method | Path | Description |
|---|---|---|
GET | /api/ai/models | List available AI models |
POST | /api/ai/models/download | Download a new AI model |
POST | /api/ai/chat | Send a chat message to an AI model |
POST | /api/rag/search | Perform a RAG search over indexed documents |
Example: Chat with a Local AI Model
curl -X POST http://localhost:8080/api/ai/chat \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"model": "llama3",
"messages": [
{
"role": "user",
"content": "How do I configure Nextcloud to use external storage?"
}
]
}'Example: RAG Search
curl -X POST http://localhost:8080/api/rag/search \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"query": "backup encryption setup",
"limit": 5
}'Audit
View the audit log of all administrative actions performed on the Cloud OS instance.
| Method | Path | Description |
|---|---|---|
GET | /api/audit/logs | List audit log entries |
Example: Get Audit Logs
curl "http://localhost:8080/api/audit/logs?limit=50" \
-H "Authorization: Bearer <token>"[
{
"id": "aud_abc123",
"action": "app.installed",
"resource": "app_def456",
"details": "Installed Nextcloud from template",
"ip_address": "192.168.1.100",
"timestamp": "2025-01-15T10:30:00Z"
},
{
"id": "aud_def456",
"action": "backup.created",
"resource": "bak_ghi789",
"details": "Manual backup to s3-primary",
"ip_address": "192.168.1.100",
"timestamp": "2025-01-15T10:25:00Z"
}
]Audit logs are immutable and cannot be deleted through the API. They are retained according to the configured retention policy.
Memory MCP
Quazzar ships a built-in Model Context Protocol server. External AI clients (Claude Desktop, Claude Code, ChatGPT, Cursor, VS Code Copilot) connect with a bearer token and read/write durable memories backed by the same notes store the UI uses.
See Memory MCP for the tool / resource / prompt surface and Memory MCP quickstart for the wiring walkthrough.
MCP transport endpoints
| Method | Path | Description |
|---|---|---|
GET / POST | /mcp | Streamable HTTP MCP endpoint (MCP spec 2025-03+). Bearer authed. |
GET | /mcp/sse | Legacy SSE event stream for older clients. |
POST | /mcp/messages | Legacy SSE JSON-RPC sink paired with /mcp/sse. |
Authorization header on every request: Authorization: Bearer quaz_....
API keys
Session-authed endpoints (used by the Settings UI) for managing MCP bearer tokens.
| Method | Path | Description |
|---|---|---|
GET | /api/mcp/keys | List every key owned by the current user (metadata only — raw bearer is never returned) |
POST | /api/mcp/keys | Create a key. Raw bearer is returned in the response once. |
DELETE | /api/mcp/keys/{id} | Revoke a key. Immediate — the client loses access on the next request. |
POST | /api/mcp/keys/{id}/rotate | Revoke the existing key and mint a new one with the same name, scopes, namespace, and expiry window. Raw bearer is returned once. |
Create key
curl -X POST http://localhost:8080/api/mcp/keys \
-H "Authorization: Bearer <session-token>" \
-H "Content-Type: application/json" \
-d '{
"name": "Claude on my laptop",
"client_type": "claude_desktop",
"namespace": "work",
"scopes": ["read_memory", "write_memory"],
"expires_in_days": 90
}'{
"id": "key_abc123",
"name": "Claude on my laptop",
"client_type": "claude_desktop",
"namespace": "work",
"scopes": ["read_memory", "write_memory"],
"key_prefix": "quaz_AB12CDE",
"expires_at": "2026-07-21T10:30:00Z",
"raw": "quaz_AB12CDEFGH345JKLMNPQRSTVWXYZ2"
}The raw field is shown only on create and rotate. Raw bearers are bcrypt-hashed on disk.
Rotate key
curl -X POST http://localhost:8080/api/mcp/keys/key_abc123/rotate \
-H "Authorization: Bearer <session-token>"Returns the same shape as create, with a new raw bearer. The old bearer is revoked in place (ErrRevoked on use).
Active sessions
| Method | Path | Description |
|---|---|---|
GET | /api/mcp/sessions | List every live MCP stream bound to the current user |
DELETE | /api/mcp/sessions/{id} | Disconnect a specific session |
curl http://localhost:8080/api/mcp/sessions \
-H "Authorization: Bearer <session-token>"[
{
"id": "sess_abc",
"client_name": "claude-desktop",
"transport": "streamable-http",
"ip": "192.168.1.42",
"connected_at": "2026-04-21T10:10:00Z",
"last_active_at": "2026-04-21T10:30:00Z"
}
]Each MCP connection is tracked as a separate session — restarting a client opens a new one.
Embedding and analytics
| Method | Path | Description |
|---|---|---|
GET | /api/mcp/memory/embed-stats | Embedding backend status, queue depth, and coverage |
POST | /api/mcp/memory/reindex | Push every note owned by the caller onto the embedding queue |
GET | /api/mcp/memory/analytics | Aggregate totals, top tags, top clients, embedding coverage over time |
curl http://localhost:8080/api/mcp/memory/embed-stats \
-H "Authorization: Bearer <session-token>"{
"queue_size": 0,
"total": 1250,
"total_with_embedding": 1247,
"backend_name": "ollama",
"backend_dim": 768,
"enabled": true
}curl -X POST http://localhost:8080/api/mcp/memory/reindex \
-H "Authorization: Bearer <session-token>"{ "enqueued": 1250, "enabled": true }Pending items collapse via the queue’s UNIQUE(note_id) constraint, so triggering a reindex on a noisy node still completes in bounded work.
Cross-node sync (Orbit Pro)
| Method | Path | Description |
|---|---|---|
GET | /api/mcp/sync/status | List enabled namespaces, timestamps, and errors |
POST | /api/mcp/sync/enable | Body: { "namespace": "work" }. Opts the namespace in to replication |
POST | /api/mcp/sync/disable | Body: { "namespace": "work" }. Pauses but keeps cursor |
POST | /api/mcp/sync/resync | Body: { "namespace": "work" }. Forces one push + one pull pass |
POST | /api/mcp/sync/reset | Body: { "namespace": "work" }. Drops cursor and outbox |
Enable returns HTTP 402 Payment Required with the canonical upgrade shape when the caller is on the Community plan.
Notes graph
Session-authed endpoints for the Orbit Notes mind-map view.
| Method | Path | Description |
|---|---|---|
GET | /api/notes/graph | Return nodes + links with filter support |
PATCH | /api/notes/{id}/meta | Adjust importance or toggle archived on any note |
GET /api/notes/graph
Every filter on the Notes mind-map toolbar maps onto a query parameter:
| Param | Values | Default |
|---|---|---|
source | Comma-separated user,mcp,molly | all three |
namespace | Comma-separated namespace names | all |
client | Comma-separated mcp_client_name values | all |
tags | Comma-separated tag names | no filter |
min_importance | 0.0..1.0 | 0 |
include_archived | 1 / 0 | 0 |
curl "http://localhost:8080/api/notes/graph?source=user,mcp&namespace=work,personal&client=claude-desktop&tags=urgent,review&min_importance=0.4&include_archived=1" \
-H "Authorization: Bearer <session-token>"{
"nodes": [
{
"id": "note_abc",
"title": "Incident 2026-04-19",
"source": "mcp",
"namespace": "work",
"client": "claude-desktop",
"importance": 0.7,
"created_at": "2026-04-19T14:32:00Z",
"updated_at": "2026-04-19T14:32:00Z",
"last_referenced_at": "2026-04-20T09:10:00Z"
}
],
"links": [
{
"source": "note_abc",
"target": "note_def",
"source_kind": "mcp",
"target_kind": "user",
"mixed_sources": true
}
]
}PATCH /api/notes/{id}/meta
curl -X PATCH http://localhost:8080/api/notes/note_abc/meta \
-H "Authorization: Bearer <session-token>" \
-H "Content-Type: application/json" \
-d '{ "importance": 0.75 }'Accepted bodies:
{ "importance": 0.75 }— set importance to a0.0..1.0value.{ "archived": true }— appends#archived, drops importance to 0.{ "archived": false }— strips#archived, leaves importance untouched.
External MCP callers route through memory_update / memory_delete instead, which enforce per-key scope checks.
Molly memory
Session-authed endpoints for Molly’s memory slash commands and settings. Not MCP — external clients use /mcp instead.
| Method | Path | Description |
|---|---|---|
POST | /api/molly/memory/remember | Persist a new memory (body: content, title?, tags?, importance?) |
POST | /api/molly/memory/recall | Hybrid search bound to the user + Molly namespace |
GET | /api/molly/memory/recent?limit=N | List the N most recently referenced Molly memories |
POST | /api/molly/memory/clear | Soft-delete every memory in the Molly namespace |
DELETE | /api/molly/memory/mcp/{id} | Soft (default) or hard (?hard=true) delete a single memory |
curl -X POST http://localhost:8080/api/molly/memory/remember \
-H "Authorization: Bearer <session-token>" \
-H "Content-Type: application/json" \
-d '{
"content": "Deployed v0.6.35 to node-01.",
"title": "Deploy 2026-04-21",
"tags": ["deploy"],
"importance": 0.7
}'All endpoints return 503 Service Unavailable when the MCP Memory service is not wired (air-gap builds).
Billing and Licensing
Feature gates for the Orbit Pro paid tier. See Orbit Pro for the tier matrix.
| Method | Path | Description |
|---|---|---|
GET | /api/license/orbit-features | Projection of the current plan’s Orbit feature gate map |
POST | /api/billing/upgrade | Start a Stripe Checkout session (proxied through the CP) |
Feature gate projection
curl http://localhost:8080/api/license/orbit-features \
-H "Authorization: Bearer <session-token>"{
"plan": "community",
"features": {
"orbit_collab": false,
"orbit_graph": false,
"orbit_full_formulas": false,
"orbit_charts": false,
"orbit_pivot": false,
"orbit_history": false,
"orbit_advanced_export": false,
"orbit_memory_sync": false,
"orbit_unlimited_shares": false,
"orbit_slides_themes": false
},
"limits": {
"share_links_max": 3,
"mcp_rate_per_minute": 30,
"version_history_hours": 24
}
}The web client uses this to render gated controls before the user clicks.
Upgrade flow
curl -X POST http://localhost:8080/api/billing/upgrade \
-H "Authorization: Bearer <session-token>" \
-H "Content-Type: application/json" \
-d '{ "plan": "pro", "return_to": "https://node.example.com/docs" }'{
"checkout_url": "https://checkout.stripe.com/c/pay/cs_live_..."
}The request is proxied through the Control Panel to Stripe. After checkout, Stripe redirects to return_to with a session id. The CC agent picks up the new plan on the next heartbeat — the node’s license.Gate is wired to the agent’s plan accessor, so the feature unlocks on the next request without a restart.
Canonical 402 response
Every gated path returns HTTP 402 Payment Required on the Community plan with this shape:
{
"error": "upgrade_required",
"feature": "orbit_pivot",
"current_plan": "community",
"required_plan": "pro",
"message": "Pivot tables require Orbit Pro.",
"upgrade_url": "/api/billing/upgrade"
}Clients catch the 402 via isUpgradeError() and render either an inline <UpgradePrompt> or open a modal <OrbitUpgradeDialog> so the user can upgrade without losing the action that triggered the gate.