# LetsBe Biz — API Reference

**Version:** 1.0
**Date:** February 26, 2026
**Authors:** Matt (Founder), Claude (Architecture)
**Status:** Engineering Spec — Ready for Implementation
**Companion docs:** Technical Architecture v1.2, Repo Analysis v1.0, Infrastructure Runbook v1.0

---

## 1. Purpose

This document is the complete API reference for the LetsBe Biz platform. It covers:

1. **Hub API** — The central platform's REST endpoints (admin, customer, tenant, webhooks)
2. **Safety Wrapper API** — The on-tenant endpoints for Hub ↔ VPS communication
3. **Authentication flows** — How each actor authenticates
4. **Webhook specifications** — Inbound and outbound webhook contracts

This reference is designed to serve as the implementation blueprint for Claude Code and Codex sessions.

---

## 2. API Overview

### 2.1 Base URLs

| API | Base URL | Auth |
|-----|----------|------|
| Hub (admin) | `https://hub.letsbe.biz/api/v1/admin/` | Bearer {staffSessionToken} |
| Hub (customer) | `https://hub.letsbe.biz/api/v1/customer/` | Bearer {customerSessionToken} |
| Hub (tenant comms) | `https://hub.letsbe.biz/api/v1/tenant/` | Bearer {hubApiKey} |
| Hub (public) | `https://hub.letsbe.biz/api/v1/public/` | None or API key |
| Hub (webhooks) | `https://hub.letsbe.biz/api/v1/webhooks/` | Signature verification |
| Safety Wrapper (local) | `http://127.0.0.1:8100/` | Internal only |
| OpenClaw Gateway (local) | `http://127.0.0.1:18789/` | Token auth |

### 2.2 Common Patterns

**Response format:** All Hub API responses follow this envelope:

```json
{
  "success": true,
  "data": { ... },
  "meta": {
    "page": 1,
    "pageSize": 25,
    "total": 142
  }
}
```

**Error format:**

```json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid email format",
    "details": [
      { "field": "email", "message": "Must be a valid email address" }
    ]
  }
}
```

**Pagination:** Cursor-based where possible, offset-based for admin lists.
- `?page=1&pageSize=25` (offset)
- `?cursor=abc123&limit=25` (cursor)

**Validation:** Zod schemas on all endpoints. 400 returned with field-level errors.

**Rate limiting:** 60 requests/minute per authenticated session. 429 returned with `Retry-After` header.

---

## 3. Authentication

### 3.1 Staff Authentication (Admin Panel)

NextAuth.js with Credentials provider, JWT sessions.

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET/POST | `/api/auth/[...nextauth]` | None | NextAuth handlers (login, logout, session) |
| POST | `/api/v1/setup` | None (one-time) | Initial owner account creation |

**Login flow:**

```
1. POST /api/auth/callback/credentials
   Body: { email, password }

2. If 2FA enabled:
   Response: { requires2FA: true, pendingToken: "..." }

3. POST /api/v1/auth/2fa/verify
   Body: { token: pendingToken, code: "123456" }
   Response: Sets session cookie

4. Session token in cookie → used as Bearer token for API calls
```

**2FA Management:**

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/v1/auth/2fa/status` | Session | Check if 2FA is enabled |
| POST | `/api/v1/auth/2fa/setup` | Session | Generate TOTP secret + QR code |
| POST | `/api/v1/auth/2fa/verify` | Session/Pending | Verify TOTP code |
| POST | `/api/v1/auth/2fa/disable` | Session | Disable 2FA (requires current code) |
| GET | `/api/v1/auth/2fa/backup-codes` | Session | Get/regenerate backup codes |

**Staff Invitations:**

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/v1/auth/invite/{token}` | None | Look up invitation details |
| POST | `/api/v1/auth/accept-invite` | None | Accept invitation, set password |

### 3.2 Customer Authentication (Customer Portal)

Same NextAuth.js flow, separate user type. Customers log in via:
- Email + password (created during Stripe checkout)
- Future: SSO via Keycloak on their tenant VPS

### 3.3 Tenant Authentication (Safety Wrapper ↔ Hub)

Bearer token authentication using a Hub-generated API key.

**Registration flow:**

```
1. During provisioning, the Hub generates a registration token per order
2. Safety Wrapper boots, calls:
   POST /api/v1/tenant/register
   Body: { registrationToken: "..." }

3. Hub validates token, returns:
   { hubApiKey: "hk_abc123..." }

4. Safety Wrapper stores hubApiKey in encrypted secrets registry
5. All subsequent requests use:
   Authorization: Bearer hk_abc123...
```

### 3.4 Stripe Webhook Authentication

Stripe webhook signature verification using `stripe.webhooks.constructEvent()`.

---

## 4. Hub Admin API (Staff)

### 4.1 Profile

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/profile` | Get current staff profile |
| PATCH | `/api/v1/profile` | Update name, email |
| POST | `/api/v1/profile/photo` | Upload profile photo (multipart/form-data → S3/MinIO) |
| POST | `/api/v1/profile/password` | Change password (requires current password) |

### 4.2 Customers

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/admin/customers` | List customers. Query: `?page=1&pageSize=25&search=acme` |
| POST | `/api/v1/admin/customers` | Create customer (staff-initiated) |
| GET | `/api/v1/admin/customers/{id}` | Get customer detail with orders, subscription |
| PATCH | `/api/v1/admin/customers/{id}` | Update customer details |

**Customer object:**

```json
{
  "id": "cust_abc123",
  "email": "maria@acme.com",
  "name": "Maria Weber",
  "company": "Acme Marketing GmbH",
  "status": "ACTIVE",
  "subscription": {
    "plan": "PRO",
    "tier": "Build",
    "tokenLimit": 15000000,
    "tokensUsed": 8234000,
    "stripeCustomerId": "cus_xxx",
    "status": "ACTIVE"
  },
  "orders": [ ... ],
  "foundingMember": {
    "number": 42,
    "tokenMultiplier": 2,
    "expiresAt": "2027-03-15T00:00:00Z"
  },
  "createdAt": "2026-03-15T10:00:00Z"
}
```

### 4.3 Orders

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/admin/orders` | List orders. Query: `?status=FULFILLED&tier=Build&search=acme` |
| POST | `/api/v1/admin/orders` | Create order (staff-initiated, MANUAL mode) |
| GET | `/api/v1/admin/orders/{id}` | Get order detail (full — server IP, tools, domain, status) |
| PATCH | `/api/v1/admin/orders/{id}` | Update order (credentials, server details) |

**Order lifecycle:**

| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/v1/admin/orders/{id}/provision` | Spawn Docker provisioner container |
| GET | `/api/v1/admin/orders/{id}/logs/stream` | SSE stream of provisioning logs |
| GET | `/api/v1/admin/orders/{id}/dns` | Get DNS verification status |
| POST | `/api/v1/admin/orders/{id}/dns/verify` | Trigger DNS A-record verification |
| POST | `/api/v1/admin/orders/{id}/dns/skip` | Manual DNS override |
| POST | `/api/v1/admin/orders/{id}/dns/create` | **NEW:** Auto-create DNS A records (Cloudflare/Entri) |
| GET | `/api/v1/admin/orders/{id}/automation` | Get automation mode (AUTO/MANUAL/PAUSED) |
| PATCH | `/api/v1/admin/orders/{id}/automation` | Change automation mode |

**Order object (key fields):**

```json
{
  "id": "ord_abc123",
  "userId": "cust_abc123",
  "status": "FULFILLED",
  "tier": "Build",
  "domain": "acme.letsbe.biz",
  "tools": ["nextcloud", "chatwoot", "ghost", "calcom", "odoo", "stalwart", "listmonk", "umami"],
  "serverIp": "88.99.xx.xx",
  "sshPort": 22022,
  "automationMode": "AUTO",
  "dashboardUrl": "https://acme.letsbe.biz",
  "portainerUrl": "https://portainer.acme.letsbe.biz",
  "createdAt": "2026-03-15T10:00:00Z",
  "fulfilledAt": "2026-03-15T12:34:56Z"
}
```

### 4.4 Container Management (via Portainer)

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/admin/orders/{id}/containers` | List all containers on the tenant VPS |
| GET | `/api/v1/admin/orders/{id}/containers/stats` | All container stats (CPU, RAM) |
| GET | `/api/v1/admin/orders/{id}/containers/{cId}` | Container detail |
| POST | `/api/v1/admin/orders/{id}/containers/{cId}/start` | Start container |
| POST | `/api/v1/admin/orders/{id}/containers/{cId}/stop` | Stop container |
| POST | `/api/v1/admin/orders/{id}/containers/{cId}/restart` | Restart container |
| GET | `/api/v1/admin/orders/{id}/containers/{cId}/logs` | Container logs. Query: `?tail=100&since=1h` |
| GET | `/api/v1/admin/orders/{id}/containers/{cId}/stats` | Container resource stats |
| GET | `/api/v1/admin/orders/{id}/portainer` | Get Portainer credentials |
| POST | `/api/v1/admin/orders/{id}/portainer/init` | Initialize Portainer endpoint |
| POST | `/api/v1/admin/orders/{id}/test-ssh` | Test SSH connectivity |

### 4.5 Servers

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/admin/servers` | List active servers (from fulfilled orders) |
| GET | `/api/v1/admin/servers/{id}/health` | Server health (heartbeat status, Safety Wrapper version) |
| POST | `/api/v1/admin/servers/{id}/command` | Queue remote command for Safety Wrapper |
| POST | `/api/v1/admin/portainer/ping` | Test Portainer connectivity |

### 4.6 Netcup Integration

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/admin/netcup/auth` | Get Netcup OAuth2 connection status |
| POST | `/api/v1/admin/netcup/auth` | Initiate OAuth2 Device Flow |
| DELETE | `/api/v1/admin/netcup/auth` | Disconnect Netcup |
| GET | `/api/v1/admin/netcup/servers` | List Netcup servers |
| GET | `/api/v1/admin/netcup/servers/{id}` | Server detail (IP, status, plan) |
| PATCH | `/api/v1/admin/netcup/servers/{id}` | Power action (start/stop/restart), hostname, nickname |
| GET | `/api/v1/admin/netcup/servers/{id}/metrics` | CPU, disk, network metrics |
| GET | `/api/v1/admin/netcup/servers/{id}/snapshots` | List snapshots |
| POST | `/api/v1/admin/netcup/servers/{id}/snapshots` | Create snapshot |
| DELETE | `/api/v1/admin/netcup/servers/{id}/snapshots/{snapId}` | Delete snapshot |
| POST | `/api/v1/admin/netcup/servers/{id}/snapshots/{snapId}/revert` | Revert to snapshot |

### 4.7 Staff Management

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/admin/staff` | List staff members |
| POST | `/api/v1/admin/staff/invite` | Send staff invitation email |
| GET | `/api/v1/admin/staff/invitations` | List pending invitations |
| PATCH | `/api/v1/admin/staff/{id}` | Update staff role/status |
| DELETE | `/api/v1/admin/staff/{id}` | Deactivate staff member |

**Roles and permissions:**

| Role | Level | Key Permissions |
|------|-------|-----------------|
| OWNER | Full | All permissions, manage staff, delete account |
| ADMIN | High | Manage customers, orders, servers, billing |
| MANAGER | Medium | View/manage customers and orders, no billing |
| SUPPORT | Low | View customers, view orders, manage containers |

### 4.8 Agent Management (NEW)

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/admin/agents/templates` | List available agent role templates |
| POST | `/api/v1/admin/orders/{id}/agents` | Deploy new agent to tenant server |
| GET | `/api/v1/admin/orders/{id}/agents` | List agents on tenant server |
| PATCH | `/api/v1/admin/orders/{id}/agents/{agentId}` | Update agent config (SOUL.md, tools, autonomy) |
| DELETE | `/api/v1/admin/orders/{id}/agents/{agentId}` | Remove agent from tenant |

**AgentConfig object:**

```json
{
  "id": "agcfg_abc123",
  "orderId": "ord_abc123",
  "agentId": "it-admin",
  "name": "IT Admin",
  "role": "it-admin",
  "soulMd": "# IT Admin\n\n## Identity\n...",
  "toolsAllowed": ["shell", "docker", "file_read", "file_write", "env_read", "env_update"],
  "toolsDenied": [],
  "toolProfile": "coding",
  "autonomyLevel": 3,
  "externalCommsUnlocks": null,
  "isActive": true
}
```

### 4.9 Command Approval Queue (NEW)

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/admin/approvals` | List all pending approvals across tenants |
| GET | `/api/v1/admin/approvals/{id}` | Approval detail with full command context |
| POST | `/api/v1/admin/approvals/{id}` | Approve or deny. Body: `{ "action": "approve" \| "deny", "reason": "..." }` |

**CommandApproval object:**

```json
{
  "id": "appr_abc123",
  "orderId": "ord_abc123",
  "agentId": "it-admin",
  "commandClass": "red",
  "toolName": "file_delete",
  "toolArgs": { "path": "/opt/letsbe/stacks/nextcloud/data/tmp/*", "recursive": true },
  "humanReadable": "IT Admin wants to delete /nextcloud/data/tmp/* (47 files, 2.3GB)",
  "status": "PENDING",
  "requestedAt": "2026-03-15T14:22:00Z",
  "expiresAt": "2026-03-16T14:22:00Z"
}
```

### 4.10 Billing & Token Metering (NEW)

| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/v1/admin/billing/usage` | Ingest token usage report from Safety Wrapper |
| GET | `/api/v1/admin/billing/{customerId}` | Customer billing summary |
| GET | `/api/v1/admin/billing/{customerId}/history` | Historical usage data |
| POST | `/api/v1/admin/billing/overages` | Trigger overage billing via Stripe |

**TokenUsageBucket object (reported by Safety Wrapper):**

```json
{
  "agentId": "marketing",
  "model": "deepseek/deepseek-v3.2",
  "bucketHour": "2026-03-15T14:00:00Z",
  "tokensInput": 45000,
  "tokensOutput": 12000,
  "tokensCacheRead": 28000,
  "tokensCacheWrite": 0,
  "webSearchCount": 2,
  "webFetchCount": 1,
  "costCents": 3
}
```

### 4.11 Analytics

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/admin/analytics/tokens` | Global token usage overview (all tenants) |
| GET | `/api/v1/admin/analytics/tokens/{orderId}` | Per-tenant token usage |
| GET | `/api/v1/admin/analytics/costs` | Cost breakdown by customer/model/agent |
| GET | `/api/v1/admin/stats` | Platform statistics (total customers, orders, revenue) |
| GET | `/api/v1/admin/analytics` | Dashboard analytics (growth, churn, usage trends) |

### 4.12 Settings

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/admin/settings` | Get all settings by category |
| PATCH | `/api/v1/admin/settings` | Update settings (bulk). Body: `{ "category.key": "value" }` |
| POST | `/api/v1/admin/settings/test-email` | Send test email |
| POST | `/api/v1/admin/settings/test-storage` | Test S3/MinIO connection |

**Settings categories:** docker, dockerhub, gitea, hub, provisioning, netcup, email, notifications, storage (50+ settings total).

### 4.13 Enterprise Client Management

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/admin/enterprise-clients` | List enterprise clients |
| POST | `/api/v1/admin/enterprise-clients` | Create enterprise client |
| GET | `/api/v1/admin/enterprise-clients/{id}` | Client detail with servers |
| PATCH | `/api/v1/admin/enterprise-clients/{id}` | Update client |
| DELETE | `/api/v1/admin/enterprise-clients/{id}` | Deactivate client |
| GET | `/api/v1/admin/enterprise-clients/{id}/servers` | List client's servers |
| POST | `/api/v1/admin/enterprise-clients/{id}/servers` | Add server to client |
| GET | `/api/v1/admin/enterprise-clients/{id}/servers/{sId}/stats` | Server stats |
| GET | `/api/v1/admin/enterprise-clients/{id}/servers/{sId}/events` | Container events |
| GET | `/api/v1/admin/enterprise-clients/{id}/error-rules` | Error detection rules |
| POST | `/api/v1/admin/enterprise-clients/{id}/error-rules` | Create error rule |
| PATCH | `/api/v1/admin/enterprise-clients/{id}/error-rules/{rId}` | Update error rule |
| DELETE | `/api/v1/admin/enterprise-clients/{id}/error-rules/{rId}` | Delete error rule |
| GET | `/api/v1/admin/enterprise-clients/{id}/errors` | Detected errors |
| POST | `/api/v1/admin/enterprise-clients/{id}/errors/{eId}/acknowledge` | Acknowledge error |
| GET | `/api/v1/admin/enterprise-clients/{id}/notifications` | Notification settings |
| PATCH | `/api/v1/admin/enterprise-clients/{id}/notifications` | Update notification settings |
| POST | `/api/v1/admin/enterprise-clients/{id}/security/verify` | Request security verification code |
| POST | `/api/v1/admin/enterprise-clients/{id}/security/confirm` | Confirm verification code |

---

## 5. Hub Customer API (NEW)

Self-service portal for customers. All endpoints require customer session authentication.

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/customer/dashboard` | Customer overview (server status, agent status, recent activity) |
| GET | `/api/v1/customer/agents` | List customer's agents with status |
| GET | `/api/v1/customer/agents/{id}` | Get agent detail (SOUL.md, tools, autonomy) |
| PATCH | `/api/v1/customer/agents/{id}` | Update agent config (personality, tools, autonomy level) |
| PATCH | `/api/v1/customer/agents/{id}/external-comms` | Update external comms gate per tool |
| GET | `/api/v1/customer/agents/{id}/activity` | Agent activity feed |
| GET | `/api/v1/customer/agents/{id}/conversations` | Conversation history |
| GET | `/api/v1/customer/usage` | Token usage summary (per agent, per model, daily/weekly/monthly) |
| GET | `/api/v1/customer/usage/breakdown` | Detailed usage breakdown for billing transparency |
| GET | `/api/v1/customer/approvals` | Pending command approval queue |
| POST | `/api/v1/customer/approvals/{id}` | Approve or deny a pending command |
| GET | `/api/v1/customer/tools` | List deployed tools with status |
| GET | `/api/v1/customer/billing` | Current billing period, usage, overages |
| GET | `/api/v1/customer/billing/history` | Billing history |
| GET | `/api/v1/customer/backups` | Backup status and history |
| PATCH | `/api/v1/customer/settings` | Update customer preferences (timezone, locale, notifications) |

---

## 6. Hub Tenant Communication API (NEW)

Endpoints called by the Safety Wrapper on each tenant VPS. All require Bearer {hubApiKey} authentication.

| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/v1/tenant/register` | Safety Wrapper registers post-deploy. Body: `{ registrationToken }`. Returns: `{ hubApiKey }` |
| POST | `/api/v1/tenant/heartbeat` | Status heartbeat. Sent every 5 minutes. |
| GET | `/api/v1/tenant/config` | Pull full config (agent configs, autonomy levels, model routing) |
| POST | `/api/v1/tenant/approval-request` | Push command approval request to Hub |
| GET | `/api/v1/tenant/approval-response/{id}` | Poll for approval/denial (or via webhook) |
| POST | `/api/v1/tenant/usage` | Report token usage buckets (hourly) |
| POST | `/api/v1/tenant/backup-status` | Report backup execution status |

**Heartbeat request body:**

```json
{
  "status": "healthy",
  "openclawVersion": "v2026.2.1",
  "safetyWrapperVersion": "v1.0.3",
  "configVersion": 7,
  "agents": {
    "dispatcher": { "status": "active", "lastActiveAt": "2026-03-15T14:20:00Z" },
    "it-admin": { "status": "active", "lastActiveAt": "2026-03-15T14:18:00Z" },
    "marketing": { "status": "idle", "lastActiveAt": "2026-03-14T09:00:00Z" },
    "secretary": { "status": "active", "lastActiveAt": "2026-03-15T14:22:00Z" },
    "sales": { "status": "idle", "lastActiveAt": "2026-03-13T16:00:00Z" }
  },
  "resources": {
    "cpuPercent": 23,
    "memoryUsedMb": 6144,
    "memoryTotalMb": 16384,
    "diskUsedGb": 45,
    "diskTotalGb": 320,
    "containersRunning": 18,
    "containersStopped": 2
  },
  "tokenUsage": {
    "periodStart": "2026-03-01T00:00:00Z",
    "tokensUsed": 8234000,
    "tokenAllotment": 15000000,
    "premiumTokensUsed": 125000,
    "premiumCostCents": 1250
  },
  "backupStatus": {
    "lastBackup": "2026-03-15T02:15:00Z",
    "status": "success",
    "sizeGb": 4.2
  }
}
```

**Heartbeat response body:**

```json
{
  "configVersion": 8,
  "configChanged": true,
  "pendingCommands": [
    { "id": "cmd_123", "type": "RESTART_SERVICE", "payload": { "service": "ghost" } }
  ],
  "pendingApprovals": [
    { "id": "appr_456", "action": "approve" }
  ]
}
```

If `configChanged: true`, the Safety Wrapper follows up with `GET /api/v1/tenant/config` to pull the updated configuration.

---

## 7. Public API

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/api/v1/public/orders` | API key | Create order from external source (Stripe checkout redirect) |
| GET | `/api/v1/public/orders/{id}` | API key | Get order status (for progress page) |
| GET | `/api/v1/public/founding-members/count` | None | Get founding member spots remaining |

---

## 8. Webhooks

### 8.1 Inbound Webhooks (Hub Receives)

**Stripe Checkout:**

| Method | Path | Source | Description |
|--------|------|--------|-------------|
| POST | `/api/v1/webhooks/stripe` | Stripe | `checkout.session.completed` → Creates User + Subscription + Order |

**Stripe webhook payload handling:**

```
Event: checkout.session.completed
→ Verify signature (stripe.webhooks.constructEvent)
→ Extract: customer email, plan, payment amount
→ Create User (status: ACTIVE)
→ Create Subscription (plan mapping: STARTER/PRO/ENTERPRISE)
→ Create Order (status: PAYMENT_CONFIRMED, mode: AUTO)
→ Automation worker picks up the order and begins provisioning
```

### 8.2 Outbound Webhooks (Hub Sends)

The Hub can push events to tenant VPS Safety Wrappers and to configured external endpoints.

**To Safety Wrapper:**

| Event | Method | Path (on tenant) | Payload |
|-------|--------|-------------------|---------|
| Config updated | POST | `{safetyWrapperUrl}/webhooks/config-update` | `{ configVersion, changedFields }` |
| Approval response | POST | `{safetyWrapperUrl}/webhooks/approval-response` | `{ approvalId, action, respondedBy }` |
| Remote command | POST | `{safetyWrapperUrl}/webhooks/command` | `{ commandId, type, payload }` |

**Webhook security:** All outbound webhooks include:
- `X-LetsBe-Signature`: HMAC-SHA256 of the request body using the shared Hub API key
- `X-LetsBe-Timestamp`: Unix timestamp (for replay protection)
- `X-LetsBe-Event`: Event type string

### 8.3 Safety Wrapper Webhook Endpoints

Endpoints exposed by the Safety Wrapper for Hub communication:

| Method | Path | Description |
|--------|------|-------------|
| POST | `/webhooks/config-update` | Hub notifies of config changes |
| POST | `/webhooks/approval-response` | Hub delivers approval/denial for gated commands |
| POST | `/webhooks/command` | Hub pushes remote commands |
| POST | `/webhooks/diun` | Diun container update notifications |
| GET | `/health` | Health check (returns Safety Wrapper + OpenClaw status) |

---

## 9. Cron Endpoints (Hub Internal)

| Method | Path | Description | Schedule |
|--------|------|-------------|----------|
| POST | `/api/v1/cron/stats-collection` | Collect stats from all tenant Portainer instances | Every 15 min |
| POST | `/api/v1/cron/stats-cleanup` | Delete stats older than 90 days | Daily at 03:00 |
| POST | `/api/v1/cron/billing-cycle` | Process monthly billing cycles | Daily at 00:00 |
| POST | `/api/v1/cron/pool-alerts` | Check token pools and send usage alerts | Every hour |
| POST | `/api/v1/cron/approval-expiry` | Expire pending approvals older than 24h | Every hour |

---

## 10. Data Models (Prisma)

### 10.1 Existing Models

| Model | Table | Primary Key | Key Relations |
|-------|-------|-------------|---------------|
| User | users | id (UUID) | → Subscription, Order[], FoundingMember? |
| Staff | staff | id (UUID) | — |
| StaffInvitation | staff_invitations | id (UUID) | — |
| Subscription | subscriptions | id (UUID) | → User |
| Order | orders | id (UUID) | → User, DnsVerification?, ProvisioningJob[], ServerConnection? |
| DnsVerification | dns_verifications | id (UUID) | → Order, DnsRecord[] |
| DnsRecord | dns_records | id (UUID) | → DnsVerification |
| ProvisioningJob | provisioning_jobs | id (UUID) | → Order, JobLog[] |
| JobLog | job_logs | id (UUID) | → ProvisioningJob |
| ProvisioningLog | provisioning_logs | id (UUID) | → Order |
| TokenUsage (legacy) | token_usage | id (UUID) | → User |
| RunnerToken | runner_tokens | id (UUID) | — |
| ServerConnection | server_connections | id (UUID) | → Order |
| RemoteCommand | remote_commands | id (UUID) | → ServerConnection |
| SystemSetting | system_settings | id (UUID) | — |

### 10.2 New Models (v1.2 Architecture)

| Model | Table | Primary Key | Key Relations |
|-------|-------|-------------|---------------|
| TokenUsageBucket | token_usage_buckets | id (UUID) | → User, Order |
| BillingPeriod | billing_periods | id (UUID) | → User, Subscription |
| FoundingMember | founding_members | id (UUID) | → User |
| AgentConfig | agent_configs | id (UUID) | → Order |
| CommandApproval | command_approvals | id (UUID) | → Order |

### 10.3 Enterprise/Monitoring Models

| Model | Table | Primary Key | Key Relations |
|-------|-------|-------------|---------------|
| EnterpriseClient | enterprise_clients | id (UUID) | → EnterpriseServer[] |
| EnterpriseServer | enterprise_servers | id (UUID) | → EnterpriseClient |
| ServerStatsSnapshot | server_stats_snapshots | id (UUID) | → EnterpriseServer, EnterpriseClient |
| ErrorDetectionRule | error_detection_rules | id (UUID) | → EnterpriseClient |
| DetectedError | detected_errors | id (UUID) | → EnterpriseServer, ErrorDetectionRule |
| SecurityVerificationCode | security_verification_codes | id (UUID) | → EnterpriseClient |
| LogScanPosition | log_scan_positions | id (UUID) | — |
| ContainerStateSnapshot | container_state_snapshots | id (UUID) | — |
| ContainerEvent | container_events | id (UUID) | — |
| NotificationSetting | notification_settings | id (UUID) | → EnterpriseClient |
| NotificationCooldown | notification_cooldowns | id (UUID) | — |
| Pending2FASession | pending_2fa_sessions | id (UUID) | — |

---

## 11. Error Codes

| Code | HTTP Status | Description |
|------|------------|-------------|
| `VALIDATION_ERROR` | 400 | Request body failed Zod validation |
| `UNAUTHORIZED` | 401 | Missing or invalid authentication |
| `FORBIDDEN` | 403 | Authenticated but insufficient permissions |
| `NOT_FOUND` | 404 | Resource does not exist |
| `CONFLICT` | 409 | Duplicate resource (e.g., duplicate email) |
| `RATE_LIMITED` | 429 | Too many requests. Check `Retry-After` header |
| `PROVISIONING_FAILED` | 500 | Server provisioning pipeline failed |
| `PORTAINER_UNAVAILABLE` | 502 | Cannot reach tenant Portainer instance |
| `NETCUP_ERROR` | 502 | Netcup API returned an error |
| `STRIPE_ERROR` | 502 | Stripe API returned an error |
| `TENANT_OFFLINE` | 503 | Tenant VPS Safety Wrapper is not responding |
| `INTERNAL_ERROR` | 500 | Unhandled server error |

---

## 12. Implementation Priority

| Phase | Endpoints | Effort |
|-------|-----------|--------|
| **Phase 1: Core** | Auth, Customers, Orders, Provisioning, DNS, Containers, Settings | Existing (retool) |
| **Phase 2: Tenant Comms** | /tenant/register, /heartbeat, /config, /usage, /approval-request | 3 weeks |
| **Phase 3: Customer Portal** | /customer/* endpoints | 3 weeks |
| **Phase 4: Agent Management** | /admin/agents/*, /customer/agents/* | 2 weeks |
| **Phase 5: Billing** | /admin/billing/*, cron jobs, Stripe integration | 3 weeks |
| **Phase 6: Analytics** | /admin/analytics/*, customer usage dashboards | 2 weeks |

---

## 13. Changelog

| Version | Date | Changes |
|---------|------|---------|
| 1.0 | 2026-02-26 | Initial API reference. 80+ existing endpoints documented. New endpoint groups: Tenant Communication (7), Customer Portal (16), Agent Management (5), Billing (4), Approvals (3), Analytics (3). Authentication flows. Webhook specs. Data models. Error codes. Implementation priorities. |
