osModa API v2
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.
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.
| Status | Meaning |
|---|---|
| 400 | Bad request — missing or invalid parameters |
| 401 | Not authenticated — missing or invalid API key |
| 403 | Forbidden — resource belongs to another user |
| 404 | Not found — server or resource doesn't exist |
| 429 | Rate limited — slow down |
| 503 | Agent not connected — server may be offline |
API Keys
Creates a new API key for programmatic access. The full key is returned only once.
Request Body
| Field | Type | Description |
|---|---|---|
| name | string | optional 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..."
}
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..."
}]
}
Permanently revokes an API key. Takes effect immediately.
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
}]
}
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
| Field | Type | Description |
|---|---|---|
| plan_id | string | required Plan ID (test, starter, developer, production) |
| ssh_key | string | optional SSH public key to add at provisioning |
| ai_provider | string | optional "anthropic" or "openai" |
| api_key | string | optional AI provider API key (encrypted for delivery) |
| region | string | optional eu-central, eu-north, us-east, us-west |
| quantity | number | optional 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).
Spawn a server using your account balance. Deducts the plan price immediately. Use this for programmatic deployments with your API key.
Request Body
| Field | Type | Description |
|---|---|---|
| plan_id | string | required Plan ID (test, starter, developer, production) |
| ssh_key | string | optional SSH public key |
| ai_provider | string | optional "anthropic" or "openai" |
| api_key | string | optional AI provider API key |
| region | string | optional Hetzner region (eu-central, eu-north, us-east, us-west) |
| quantity | number | optional 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
| Status | Meaning |
|---|---|
| 402 | Insufficient balance — add funds first |
| 400 | Invalid plan_id or region |
Spawn a server with direct crypto payment (payment_tx + payment_chain). For automated deployments, use /api/dashboard/deploy with account balance instead.
Request Body
| Field | Type | Description |
|---|---|---|
| plan_id | string | required Plan ID |
| payment_tx | string | required Transaction hash |
| payment_chain | string | required "base" or "solana" |
| ssh_key | string | optional SSH public key |
| ai_provider | string | optional "anthropic" or "openai" |
| api_key | string | optional AI provider API key |
| region | string | optional Hetzner region |
Destroys the Hetzner server and marks the order as deleted. Prorated refund is credited to your balance.
Wipes the server and reinstalls osModa from scratch. All data on the server is lost.
Request Body
| Field | Type | Description |
|---|---|---|
| plan_id | string | required 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.
openclaw.json and restart the gateway. Changes take effect within seconds. For the machine-readable agent skill doc, see /SKILL.md.
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"
}
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
| Field | Type | Description |
|---|---|---|
| agentId | string | required Lowercase, 2-32 chars: [a-z][a-z0-9_-]* |
| model | string | optional Model ID (default: claude-sonnet-4-6) |
| channels | string[] | optional Channel bindings: telegram, whatsapp, discord, slack, web |
Valid Models
| Model ID | Provider |
|---|---|
| anthropic/claude-opus-4-6 | Anthropic |
| anthropic/claude-sonnet-4-6 | Anthropic |
| anthropic/claude-haiku-4-5-20251001 | Anthropic |
| openai/gpt-4o | OpenAI |
| openai/o3-mini | OpenAI |
Response
{
"created": true,
"agentId": "researcher",
"model": "anthropic/claude-sonnet-4-6",
"channels": ["telegram"]
}
Updates the model and/or channel bindings for an existing agent. Restarts the gateway.
Request Body
| Field | Type | Description |
|---|---|---|
| model | string | optional New model ID (see valid models above) |
| channels | string[] | optional New channel bindings (replaces existing) |
Removes the agent from openclaw.json and its channel bindings. Restarts the gateway. Cannot delete the default osmoda agent.
Configuration
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
| Field | Type | Description |
|---|---|---|
| api_key | string | required Your AI provider API key (max 256 chars) |
| provider | string | optional "anthropic" (default) or "openai" |
How it works
- Your API key is encrypted with AES-256-GCM using a key derived from the server's heartbeat secret
- The encrypted key is queued as a pending action
- On the next heartbeat (~60s), the server pulls and decrypts the key locally
- The key is written to the server's auth config and the AI gateway restarts
- The plaintext key never touches our database
Removes the AI provider configuration and stops the agent gateway on the server.
Response
{
"ssh_keys": ["ssh-ed25519 AAAA... user@host"]
}
Adds an SSH public key to the server's authorized_keys via the next heartbeat.
Request Body
| Field | Type | Description |
|---|---|---|
| ssh_key | string | required SSH public key (ssh-ed25519/ssh-rsa/ecdsa-sha2) |
Removes the SSH key at the given index (0-based). Get the index from the list endpoint.
Chat
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
| Field | Type | Description |
|---|---|---|
| message | string | required Message text (max 10,000 chars) |
Query Parameters
| Param | Type | Description |
|---|---|---|
| timeout | number | optional 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"
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
Returns provisioning and lifecycle log entries for the server.
SSHs into the server and returns recent journalctl output. Query params: lines (default 100), unit (systemd unit filter).
Lists files in a directory on the server via SSH. Query param: path (default /root).
Reads a file from the server via SSH. Query param: path (required, absolute path).
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.
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
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..."
}]
}
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
| Field | Type | Description |
|---|---|---|
| amount | number | required 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
Returns all support tickets created by you.
Request Body
| Field | Type | Description |
|---|---|---|
| subject | string | required Ticket subject |
| message | string | required Initial message |
| category | string | optional general, technical, billing, feature_request |
| server_id | string | optional Related server ID |
Request Body
| Field | Type | Description |
|---|---|---|
| message | string | required 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.
Returns available plans with x402 payment details. Accepts USDC on Base (EVM) or Solana (SVM).
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
| Field | Type | Description |
|---|---|---|
| string | required Notification email | |
| ssh_key | string | optional SSH public key |
| ai_provider | string | optional "anthropic" or "openai" |
| api_key | string | optional 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"
}
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 Group | Limit |
|---|---|
| Server list / status | 60 req/min |
| Chat (REST) | 10 req/min per server |
| Spawn / Checkout | 5 req/min |
| SSH operations (logs, files) | 20 req/min per server |
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.
| Header | Value |
|---|---|
| Authorization | Bearer osk_... |
| Content-Type | application/json |
| Body | Type | Description |
|---|---|---|
| message | string | The message to send (max 10,000 chars) |
| Query | Type | Description |
|---|---|---|
| timeout | number | Response 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.
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 Param | Description |
|---|---|
| token | Your 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" }));
Retrieve conversation history from the server. Returns messages from all channels (Telegram, dashboard, etc.) in chronological order.
| Header | Value |
|---|---|
| Authorization | Bearer osk_... |
| Query | Type | Description |
|---|---|---|
| agent | string | all, osmoda, or mobile (default: all) |
| limit | number | Max messages to return (default: 100, max: 500) |
| since | string | ISO 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
}
List directory contents on the server.
| Header | Value |
|---|---|
| Authorization | Bearer osk_... |
| Query | Type | Description |
|---|---|---|
| path | string | Directory 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 }
]
}
Upload a file to the server. Max 10MB. Allowed paths: /root, /tmp, /home, /var/lib/osmoda.
| Header | Value |
|---|---|
| Authorization | Bearer osk_... |
| Content-Type | application/json |
// Request
{ "path": "/root/data.csv", "content": "base64-encoded-content", "encoding": "base64" }
// Response
{ "uploaded": true, "path": "/root/data.csv", "size": 15234 }