osModa API v2

Last updated: 2026-03-22 8 endpoints REST + WebSocket

Programmatic access to your osModa servers. Spawn infrastructure, manage agents, send messages to your AI, configure settings, and monitor everything — all from code.

Base URL: https://spawn.os.moda
Agent-readable version: /SKILL.md — plain text for AI agents to consume

Authentication

All API requests require authentication via an API key sent in the Authorization header.

Authorization: Bearer sk_live_a1b2c3d4e5f6...

Generate an API key

Create a key from the dashboard settings or via the POST /api/keys endpoint (requires being logged in first). Keys are shown once at creation — store them securely.

API keys have full access to your account. Never share them, commit them to git, or expose them client-side. If a key is compromised, revoke it immediately via DELETE /api/keys/:id.

Quick Start

# List your servers
curl -H "Authorization: Bearer sk_live_YOUR_KEY" \
  https://spawn.os.moda/api/dashboard/servers

# Send a message to your agent
curl -X POST \
  -H "Authorization: Bearer sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"message": "Run a health check"}' \
  https://spawn.os.moda/api/dashboard/servers/SERVER_ID/chat

# Spawn a new server (deducts from account balance)
curl -X POST \
  -H "Authorization: Bearer sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"plan_id": "starter", "region": "eu-central"}' \
  https://spawn.os.moda/api/dashboard/deploy

# Set your Anthropic API key (encrypted, never stored in plaintext)
curl -X POST \
  -H "Authorization: Bearer sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"api_key": "sk-ant-...", "provider": "anthropic"}' \
  https://spawn.os.moda/api/dashboard/servers/SERVER_ID/api-key

Errors

All errors return JSON with an error field.

StatusMeaning
400Bad request — missing or invalid parameters
401Not authenticated — missing or invalid API key
403Forbidden — resource belongs to another user
404Not found — server or resource doesn't exist
429Rate limited — slow down
503Agent not connected — server may be offline

API Keys

POST /api/keys Generate a new API key

Creates a new API key for programmatic access. The full key is returned only once.

Request Body

FieldTypeDescription
namestringoptional Display name for the key (max 64 chars)

Response

{
  "key": "sk_live_a1b2c3d4e5f6...",  // Save this! Never shown again
  "id": "uuid",
  "name": "my-bot",
  "prefix": "sk_live_a1b2c3d4..."
}
GET /api/keys List all API keys (masked)

Returns all active API keys. Full keys are never returned — only the prefix.

Response

{
  "keys": [{
    "id": "uuid", "name": "my-bot",
    "prefix": "sk_live_a1b2c3d4...",
    "created_at": "2026-03-07T...",
    "last_used": "2026-03-07T..."
  }]
}
DELETE /api/keys/:id Revoke an API key

Permanently revokes an API key. Takes effect immediately.

Servers

GET /api/dashboard/servers List all your servers

Returns all servers owned by the authenticated user with full status, health data, agent info, and channel connections.

Response

{
  "servers": [{
    "id": "uuid",
    "plan": "solo",
    "plan_name": "Solo Agent",
    "plan_price": 14.99,
    "status": "running",
    "server_ip": "1.2.3.4",
    "server_name": "osmoda-fb8f1012",
    "ssh_command": "ssh root@1.2.3.4",
    "last_heartbeat": "2026-03-07T...",
    "last_health": { "cpu": 12, "ram": 45, "disk": 23 },
    "agents": [{ "name": "osmoda", "status": "running", "model": "anthropic/claude-opus-4-6", "channels": [], "default": true }],
    "daemon_health": { ... },
    "ai_provider": "anthropic",
    "has_api_key": true,
    // ... 30+ more fields
  }]
}
POST /api/dashboard/checkout Spawn server (Stripe payment)

Creates a Stripe checkout session for a new server. Returns a URL to redirect the user to for payment. Once paid, the server is provisioned automatically.

Request Body

FieldTypeDescription
plan_idstringrequired Plan ID (test, starter, developer, production)
ssh_keystringoptional SSH public key to add at provisioning
ai_providerstringoptional "anthropic" or "openai"
api_keystringoptional AI provider API key (encrypted for delivery)
regionstringoptional eu-central, eu-north, us-east, us-west
quantitynumberoptional Number of servers (1-20, default 1)

Example

curl -X POST https://spawn.os.moda/api/dashboard/checkout \
  -H "Authorization: Bearer sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"plan_id":"starter","region":"eu-central"}'

Response

{
  "checkout_url": "https://checkout.stripe.com/c/pay/cs_live_...",
  "order_ids": ["550e8400-e29b-41d4-a716-446655440000"]
}

Redirect the user to checkout_url. After payment, the server deploys automatically (~8 min).

POST /api/dashboard/deploy Spawn server (account balance)

Spawn a server using your account balance. Deducts the plan price immediately. Use this for programmatic deployments with your API key.

Request Body

FieldTypeDescription
plan_idstringrequired Plan ID (test, starter, developer, production)
ssh_keystringoptional SSH public key
ai_providerstringoptional "anthropic" or "openai"
api_keystringoptional AI provider API key
regionstringoptional Hetzner region (eu-central, eu-north, us-east, us-west)
quantitynumberoptional Number of servers (1-20, default 1)

Example

curl -X POST https://spawn.os.moda/api/dashboard/deploy \
  -H "Authorization: Bearer sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"plan_id":"starter","region":"eu-central"}'

Response

{
  "success": true,
  "order_ids": ["uuid"],
  "quantity": 1,
  "plan": "Starter",
  "balance_remaining": 4501,
  "message": "Server deploying from balance. Allow 5-10 minutes."
}

Errors

StatusMeaning
402Insufficient balance — add funds first
400Invalid plan_id or region
POST /api/dashboard/spawn Spawn server (crypto payment)

Spawn a server with direct crypto payment (payment_tx + payment_chain). For automated deployments, use /api/dashboard/deploy with account balance instead.

Request Body

FieldTypeDescription
plan_idstringrequired Plan ID
payment_txstringrequired Transaction hash
payment_chainstringrequired "base" or "solana"
ssh_keystringoptional SSH public key
ai_providerstringoptional "anthropic" or "openai"
api_keystringoptional AI provider API key
regionstringoptional Hetzner region
DELETE /api/dashboard/servers/:id Delete a server

Destroys the Hetzner server and marks the order as deleted. Prorated refund is credited to your balance.

POST /api/dashboard/servers/:id/rebuild Rebuild server from scratch

Wipes the server and reinstalls osModa from scratch. All data on the server is lost.

This is destructive. The server will be wiped and re-provisioned.
POST /api/dashboard/servers/:id/upgrade Upgrade server plan

Request Body

FieldTypeDescription
plan_idstringrequired New plan ID (must be higher tier)

Agent Management

Each osModa server runs multiple AI agents via OpenClaw multi-agent. The default osmoda agent (Opus) handles web chat and system management. Additional agents can be created for different models, channels, or use cases.

Agent management operations SSH into your server to modify openclaw.json and restart the gateway. Changes take effect within seconds. For the machine-readable agent skill doc, see /SKILL.md.
GET /api/dashboard/servers/:id/agents List agents (live from server)

Reads openclaw.json live from the server via SSH. Falls back to cached heartbeat data if SSH fails.

Response

{
  "agents": [
    {
      "id": "osmoda",
      "name": "osModa",
      "model": "anthropic/claude-opus-4-6",
      "default": true,
      "channels": []
    },
    {
      "id": "mobile",
      "name": "mobile",
      "model": "anthropic/claude-sonnet-4-6",
      "default": false,
      "channels": ["telegram", "whatsapp"]
    }
  ],
  "source": "live"
}
POST /api/dashboard/servers/:id/agents Create a new agent

Creates a new agent on the server: adds to openclaw.json, creates workspace + session dirs, copies auth profile from the default agent, and restarts the gateway.

Request Body

FieldTypeDescription
agentIdstringrequired Lowercase, 2-32 chars: [a-z][a-z0-9_-]*
modelstringoptional Model ID (default: claude-sonnet-4-6)
channelsstring[]optional Channel bindings: telegram, whatsapp, discord, slack, web

Valid Models

Model IDProvider
anthropic/claude-opus-4-6Anthropic
anthropic/claude-sonnet-4-6Anthropic
anthropic/claude-haiku-4-5-20251001Anthropic
openai/gpt-4oOpenAI
openai/o3-miniOpenAI

Response

{
  "created": true,
  "agentId": "researcher",
  "model": "anthropic/claude-sonnet-4-6",
  "channels": ["telegram"]
}
PUT /api/dashboard/servers/:id/agents/:agentId Update agent model or channels

Updates the model and/or channel bindings for an existing agent. Restarts the gateway.

Request Body

FieldTypeDescription
modelstringoptional New model ID (see valid models above)
channelsstring[]optional New channel bindings (replaces existing)
DELETE /api/dashboard/servers/:id/agents/:agentId Remove an agent

Removes the agent from openclaw.json and its channel bindings. Restarts the gateway. Cannot delete the default osmoda agent.

This removes the agent config immediately. Agent workspace and session data remain on disk for manual recovery.

Configuration

Security: AI provider API keys are encrypted with the server's unique heartbeat secret (AES-256-GCM) before storage. They are never stored in plaintext, never returned in API responses, and are decrypted only on the server itself during delivery. Even osModa operators cannot read your keys.
POST /api/dashboard/servers/:id/api-key Set AI provider API key

Configures the AI provider and API key for your server's agent. The key is encrypted before storage and delivered to the server via the next heartbeat.

Request Body

FieldTypeDescription
api_keystringrequired Your AI provider API key (max 256 chars)
providerstringoptional "anthropic" (default) or "openai"

How it works

  1. Your API key is encrypted with AES-256-GCM using a key derived from the server's heartbeat secret
  2. The encrypted key is queued as a pending action
  3. On the next heartbeat (~60s), the server pulls and decrypts the key locally
  4. The key is written to the server's auth config and the AI gateway restarts
  5. The plaintext key never touches our database
DELETE /api/dashboard/servers/:id/api-key Remove AI provider key

Removes the AI provider configuration and stops the agent gateway on the server.

GET /api/dashboard/servers/:id/ssh-keys List SSH keys

Response

{
  "ssh_keys": ["ssh-ed25519 AAAA... user@host"]
}
POST /api/dashboard/servers/:id/ssh-keys Add SSH key

Adds an SSH public key to the server's authorized_keys via the next heartbeat.

Request Body

FieldTypeDescription
ssh_keystringrequired SSH public key (ssh-ed25519/ssh-rsa/ecdsa-sha2)
DELETE /api/dashboard/servers/:id/ssh-keys/:index Remove SSH key by index

Removes the SSH key at the given index (0-based). Get the index from the list endpoint.

Chat

POST /api/dashboard/servers/:id/chat Send message to agent (REST)

Sends a message to the server's AI agent and waits for the complete response. This is the simplest way to interact with your agent programmatically.

Request Body

FieldTypeDescription
messagestringrequired Message text (max 10,000 chars)

Query Parameters

ParamTypeDescription
timeoutnumberoptional Max wait in seconds (10-300, default 120)

Response

{
  "response": "System health check complete. CPU: 12%, RAM: 45%...",
  "truncated": false
}

Example

curl -X POST \
  -H "Authorization: Bearer sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"message": "What services are running?"}' \
  "https://spawn.os.moda/api/dashboard/servers/SERVER_ID/chat?timeout=60"
GET wss://spawn.os.moda/ws/:orderId?role=dash WebSocket chat (streaming)

For real-time streaming responses, connect via WebSocket. Requires cookie session auth (browser) or use the v1 chat endpoint with osk_ token.

v1 WebSocket (API token)

// Connect with osk_ token from v1 spawn
const ws = new WebSocket("wss://spawn.os.moda/api/v1/chat/ORDER_ID?token=osk_...");
ws.send(JSON.stringify({ type: "chat", text: "Hello agent" }));
ws.onmessage = (e) => {
  const msg = JSON.parse(e.data);
  // msg.event === "agent" && msg.payload.stream === "assistant"
  // msg.payload.data.delta = incremental text chunk
};

Monitoring

GET /api/dashboard/servers/:id/logs Get server provisioning logs

Returns provisioning and lifecycle log entries for the server.

GET /api/dashboard/servers/:id/live-logs Get live journalctl logs via SSH

SSHs into the server and returns recent journalctl output. Query params: lines (default 100), unit (systemd unit filter).

GET /api/dashboard/servers/:id/files List server directory

Lists files in a directory on the server via SSH. Query param: path (default /root).

GET /api/dashboard/servers/:id/file-content Read file from server

Reads a file from the server via SSH. Query param: path (required, absolute path).

GET /api/dashboard/servers/:id/apps Detect running applications

Scans the server for running web apps and services. Detects 8 types: systemd services, home/root projects, listening ports, Docker/Podman containers, nginx vhosts, Caddy domains, and osModa managed apps. Also returned in heartbeat data every 5 minutes.

POST /api/dashboard/servers/:id/trigger-heartbeat Force immediate heartbeat sync

SSHes into the server and triggers the heartbeat script immediately (instead of waiting for the 5-minute timer). Delivers any pending actions (SSH keys, API keys, channel connections) and refreshes all server data (health, agents, apps, daemons).

Response

{
  "triggered": true,
  "pending_count": 0,
  "message": "All actions synced successfully"
}

Billing

GET /api/dashboard/balance Get balance, burn rate, and transactions

Returns your current account balance, monthly burn rate from running servers, and transaction history.

Example

curl -H "Authorization: Bearer sk_live_YOUR_KEY" \
  https://spawn.os.moda/api/dashboard/balance

Response

{
  "balance": 10000,              // cents ($100.00)
  "balance_formatted": "$100.00",
  "monthly_burn": 1499,           // cents/month from running servers
  "daily_burn": 50,               // cents/day
  "days_remaining": 200,           // days until balance runs out
  "active_servers": 1,
  "transactions": [{
    "id": "uuid",
    "type": "admin_credit",
    "amount": 10000,
    "description": "Admin credit — $100.00",
    "created_at": "2026-03-09T..."
  }]
}
POST /api/dashboard/deposit Add funds (Stripe)

Creates a Stripe checkout session to add funds to your account balance. Returns a URL to complete payment. After payment, the balance is credited automatically.

Request Body

FieldTypeDescription
amountnumberrequired Amount in USD ($5 – $10,000)

Example

curl -X POST https://spawn.os.moda/api/dashboard/deposit \
  -H "Authorization: Bearer sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount": 50}'

Response

{
  "checkout_url": "https://checkout.stripe.com/c/pay/cs_live_...",
  "amount": "$50.00"
}

Redirect the user to checkout_url to complete the payment. Balance is credited instantly after payment.

Support Tickets

GET /api/dashboard/tickets List your tickets

Returns all support tickets created by you.

POST /api/dashboard/tickets Create a support ticket

Request Body

FieldTypeDescription
subjectstringrequired Ticket subject
messagestringrequired Initial message
categorystringoptional general, technical, billing, feature_request
server_idstringoptional Related server ID
POST /api/dashboard/tickets/:id/reply Reply to a ticket

Request Body

FieldTypeDescription
messagestringrequired Reply message

x402 Protocol (Agent-to-Agent)

The x402 endpoints allow AI agents to spawn servers programmatically using USDC payments on Base or Solana. No account needed — pay per request.

GET /api/v1/plans List plans with x402 pricing Free

Returns available plans with x402 payment details. Accepts USDC on Base (EVM) or Solana (SVM).

POST /api/v1/spawn/:planId Spawn server (x402 payment) x402

Spawns a server with x402 USDC payment (Base or Solana). Returns an osk_ API token for managing the server.

Supported chains: Base (EVM, chain ID 8453) — Solana (SVM, mainnet-beta). The 402 response advertises both networks; your x402 client picks one.

Request Body

FieldTypeDescription
emailstringrequired Notification email
ssh_keystringoptional SSH public key
ai_providerstringoptional "anthropic" or "openai"
api_keystringoptional AI provider key

Response

{
  "order_id": "uuid",
  "api_token": "osk_...",  // Save this for v1 status/chat
  "status": "provisioning",
  "status_url": "https://spawn.os.moda/api/v1/status/uuid",
  "chat_url": "wss://spawn.os.moda/api/v1/chat/uuid"
}
GET /api/v1/status/:orderId Server status

Basic status is free. Full details (IP, health, agents) require Authorization: Bearer osk_...

Rate Limits

API keys share the same rate limits as dashboard sessions. If you receive a 429 response, back off and retry.

Endpoint GroupLimit
Server list / status60 req/min
Chat (REST)10 req/min per server
Spawn / Checkout5 req/min
SSH operations (logs, files)20 req/min per server
POST /api/v1/chat/:orderId/message

Send a message to the agent and receive the complete response. The agent has full access to all 90 tools (shell, files, services, network, etc.). Timeout configurable via query param.

HeaderValue
AuthorizationBearer osk_...
Content-Typeapplication/json
BodyTypeDescription
messagestringThe message to send (max 10,000 chars)
QueryTypeDescription
timeoutnumberResponse timeout in seconds (default: 120, min: 10, max: 300)
// Request
POST /api/v1/chat/ORDER_ID/message
{ "message": "Check server health" }

// Response
{ "text": "All systems healthy. CPU: 12%, RAM: 45%, Disk: 23%.", "agent": "osmoda" }

// Error response
{ "error": "Chat failed: missing scope: operator.write" }

// Timeout response
{ "text": "partial response so far...", "timeout": true, "agent": "osmoda" }

Rate limit: 10 messages per minute per server.

WS wss://spawn.os.moda/api/v1/chat/:orderId?token=osk_...

Real-time WebSocket connection to the agent. Receive streaming responses character-by-character, tool use events, and lifecycle updates. This is the same protocol used by the dashboard chat.

Query ParamDescription
tokenYour osk_ API token
// Connect
const ws = new WebSocket("wss://spawn.os.moda/api/v1/chat/ORDER_ID?token=osk_...");

// Send a message
ws.send(JSON.stringify({ type: "chat", text: "Hello" }));

// Receive: agent connected status
{ "type": "status", "agent_connected": true }

// Receive: agent thinking
{ "type": "event", "event": "agent", "payload": { "stream": "lifecycle", "data": { "phase": "start" } } }

// Receive: streaming text (character by character)
{ "type": "event", "event": "agent", "payload": { "stream": "assistant", "data": { "text": "All sys", "delta": "sys" } } }

// Receive: tool use
{ "type": "event", "event": "agent", "payload": { "stream": "tool_use", "data": { "name": "system_health" } } }

// Receive: response complete
{ "type": "event", "event": "agent", "payload": { "stream": "lifecycle", "data": { "phase": "end" } } }

// Send: abort current response
ws.send(JSON.stringify({ type: "chat_abort" }));
GET /api/v1/servers/:orderId/messages

Retrieve conversation history from the server. Returns messages from all channels (Telegram, dashboard, etc.) in chronological order.

HeaderValue
AuthorizationBearer osk_...
QueryTypeDescription
agentstringall, osmoda, or mobile (default: all)
limitnumberMax messages to return (default: 100, max: 500)
sincestringISO timestamp — only return messages after this time
{
  "messages": [
    { "id": "abc123", "role": "user", "text": "Check server status", "channel": "telegram", "agent": "mobile", "sender": "Alice", "ts": "2026-03-21T08:00:00Z" },
    { "id": "def456", "role": "assistant", "text": "All systems healthy.", "channel": "telegram", "agent": "mobile", "model": "claude-sonnet-4-6", "ts": "2026-03-21T08:00:05Z" }
  ],
  "total": 42,
  "truncated": false
}
GET /api/v1/servers/:orderId/files

List directory contents on the server.

HeaderValue
AuthorizationBearer osk_...
QueryTypeDescription
pathstringDirectory path (default: /root). Allowed: /root, /etc/osmoda, /etc/nixos, /var/lib/osmoda, /opt/osmoda, /home, /tmp
{
  "type": "dir",
  "path": "/root",
  "entries": [
    { "name": "project", "type": "dir", "size": 4096, "mtime": 1711000000 },
    { "name": "data.csv", "type": "file", "size": 15234, "mtime": 1711000000 }
  ]
}
POST /api/v1/servers/:orderId/upload

Upload a file to the server. Max 10MB. Allowed paths: /root, /tmp, /home, /var/lib/osmoda.

HeaderValue
AuthorizationBearer osk_...
Content-Typeapplication/json
// Request
{ "path": "/root/data.csv", "content": "base64-encoded-content", "encoding": "base64" }

// Response
{ "uploaded": true, "path": "/root/data.csv", "size": 15234 }