packages/cloud-shared/README.md
Shared backend code for Eliza Cloud — billing arithmetic, db schemas/repositories/migrations, server services (Discord/Telegram/Twitter/Hetzner/sandbox/etc.), and Cloudflare-worker types.
Consumed by:
@elizaos/cloud-api — Cloudflare Worker (Hono)@elizaos/cloud-frontend — Vite + React 19, deployed to Cloudflare Pages@elizaos/cloud-services/* — auxiliary Node servicesplugin-elizacloud via @elizaos/cloud-sdk, plugin-tailscale/plugin-wallet/plugin-streaming via @elizaos/cloud-routing)See CLAUDE.md for layout, commands, migration workflow, and architecture rules.
Much of the prose below predates the consolidation and describes the old cloud/ monorepo root (Next.js + nested workspaces). Treat it as historical reference for product/business context; defer to CLAUDE.md and the actual source for current structure.
Eliza Cloud V2 is a full-stack AI-as-a-Service platform that combines:
container-control-plane (Hetzner) and Railway for managed servicesText & Chat:
/api/v1/messages endpoint for Claude Code and Anthropic SDK clientsImage Creation:
Video Generation:
Music & Voice:
Full Agent Runtime:
@elizaos/core with PostgreSQL databaseCharacter Creator:
Agent Chat Interface:
Billing System:
API Key Management:
Container Deployments:
container-control-plane service on HetznerDashboard:
Gallery:
Analytics:
Enterprise Auth:
Billing Integration:
Type Safety:
cloud/
├── app/ # Next.js App Router
│ ├── api/ # API routes
│ │ ├── v1/ # Versioned API
│ │ │ ├── chat/ # AI text generation
│ │ │ ├── generate-image/ # Image generation
│ │ │ ├── generate-video/ # Video generation
│ │ │ ├── gallery/ # Media gallery
│ │ │ ├── containers/ # Container management (Hetzner via container-control-plane)
│ │ │ ├── api-keys/ # API key CRUD
│ │ │ ├── character-assistant/ # Character creator AI
│ │ │ ├── user/ # User info
│ │ │ └── models/ # Available AI models
│ │ ├── eliza/ # elizaOS agent API
│ │ │ └── rooms/ # Agent rooms and messages
│ │ ├── stripe/ # Stripe webhooks and checkout
│ │ └── fal/ # Fal.ai proxy
│ ├── dashboard/ # Protected dashboard pages
│ │ ├── text/ # Text chat interface
│ │ ├── image/ # Image generation studio
│ │ ├── video/ # Video generation studio
│ │ ├── gallery/ # Generated media gallery
│ │ ├── containers/ # Container management UI
│ │ ├── api-keys/ # API key management
│ │ ├── billing/ # Credits and billing
│ │ ├── analytics/ # Usage analytics
│ │ ├── account/ # Account settings
│ │ ├── character-creator/ # Character builder
│ │ ├── eliza/ # elizaOS agent chat
│ │ └── storage/ # Storage management
│ ├── actions/ # Server actions
│ │ ├── auth.ts # Auth actions
│ │ ├── gallery.ts # Gallery actions
│ │ ├── characters.ts # Character CRUD
│ │ ├── conversations.ts # Conversation management
│ │ └── users.ts # User actions
│ ├── layout.tsx # Root layout with analytics
│ ├── page.tsx # Landing page
│ └── globals.css # Global styles (Tailwind)
├── components/ # React components
│ ├── chat/ # Chat interfaces
│ ├── image/ # Image generation UI
│ ├── video/ # Video generation UI
│ ├── gallery/ # Gallery grid and display
│ ├── containers/ # Container tables
│ ├── api-keys/ # API key management UI
│ ├── billing/ # Credit packs and billing
│ ├── character-creator/ # Character builder UI
│ ├── dashboard/ # Dashboard metrics and cards
│ ├── layout/ # Header, sidebar, navigation
│ ├── theme/ # Theme provider and toggle
│ ├── ui/ # Reusable UI components (45+ components)
│ └── ai-elements/ # AI-specific UI components
├── db/ # Database layer
│ ├── sass/ # SaaS platform schema
│ │ └── schema.ts # Organizations, users, API keys, credits, etc.
│ ├── eliza/ # elizaOS runtime schema
│ │ └── schema.ts # Agents, memories, rooms, embeddings, etc.
│ ├── drizzle.ts # Database client
│ └── migrations/ # Migration SQL files
├── lib/ # Shared utilities
│ ├── queries/ # Database queries (12 files)
│ │ ├── api-keys.ts # API key operations
│ │ ├── credits.ts # Credit transactions
│ │ ├── containers.ts # Container CRUD
│ │ ├── container-quota.ts # Quota enforcement
│ │ ├── generations.ts # Media generation records
│ │ ├── usage.ts # Usage tracking
│ │ └── ...
│ ├── services/ # Business logic services
│ │ ├── health-monitor.ts # Provider health checks
│ │ └── containers.ts # Container management (Hetzner / container-control-plane)
│ ├── eliza/ # elizaOS integration
│ │ ├── agent-runtime.ts # AgentRuntime wrapper
│ │ ├── agent.ts # Agent management
│ │ └── plugin-cloud-bootstrap/ # Cloud bootstrap plugin (name: "cloud-bootstrap")
│ ├── config/ # Configuration
│ │ ├── env-validator.ts # Environment validation
│ │ ├── env-consolidation.ts # Config helpers
│ │ └── startup.ts # Startup checks
│ ├── errors/ # Custom error classes
│ ├── middleware/ # Middleware utilities
│ ├── auth.ts # Auth helpers
│ ├── blob.ts # R2 storage utilities
│ ├── stripe.ts # Stripe client
│ ├── pricing.ts # Cost calculations
│ ├── rate-limiter.ts # Rate limiting
│ ├── utils.ts # General utilities
│ └── types.ts # Shared TypeScript types
├── .env.example # Environment template
├── docs/ # Detailed documentation
│ ├── API_REFERENCE.md # Complete API reference
│ ├── anthropic-cot-budget.md # ANTHROPIC_COT_BUDGET + provider merge WHYs
│ ├── unit-testing-agent-mocks.md # Bun mock.module + Agent pricing test WHYs
│ ├── ROADMAP.md # Product direction and done items
│ ├── DEPLOYMENT.md # Deployment guide
│ ├── DEPLOYMENT_TROUBLESHOOTING.md # Troubleshooting
│ ├── STRIPE_SETUP.md # Stripe integration
│ ├── ENV_VARIABLES.md # Environment configuration
│ └── ...
├── scripts/ # Utility scripts
│ ├── seed-credit-packs.ts
│ └── ...
├── middleware.ts # Next.js middleware (auth)
├── drizzle.config.ts # Drizzle Kit config
└── package.json # Dependencies
graph TD
A[Client Request] --> B[Next.js Middleware]
B --> C{Auth Required?}
C -->|Yes| D[Steward Session / API Key Auth]
C -->|No| E[Route Handler]
D -->|Authenticated| E
D -->|Unauthenticated| F[Redirect to Login]
E --> G{Request Type}
G -->|AI Chat| H[OpenRouter]
G -->|Image/Video| I[Configured Providers]
G -->|Data| J[Drizzle ORM]
G -->|Container| K[Hetzner / container-control-plane]
G -->|elizaOS| L[AgentRuntime]
H --> M[Response]
I --> M
J --> N[PostgreSQL]
K --> M
L --> N
N --> M
The platform uses a single database with integrated schemas:
db/schemas/*.ts): Platform infrastructure
db/schemas/organizations.ts), users (db/schemas/users.ts), authenticationdb/schemas/api-keys.ts), usage tracking (db/schemas/usage-records.ts)db/schemas/credit-transactions.ts, db/schemas/credit-packs.ts), billing, Stripe integrationdb/schemas/containers.ts), Hetzner-backed container deploymentsdb/schemas/generations.ts - image/video records)db/schemas/conversations.ts - platform-level chat)@elizaos/plugin-sql schema):
steward-token HTTP-only session cookie for browser flowsAuthorization: Bearer <steward-jwt> for handlers that accept session bearer tokensAuthorization: Bearer eliza_<secret> or X-API-Key: eliza_<secret> for programmatic API access@aws-sdk/client-s3 pointed at the R2 endpointcontainer-control-plane: Container deployment backendgateway-discord and gateway-webhook servicesNeon Database (neon.tech)
Steward auth
/steward mount in local/frontend flows when available.env.example, packages/lib/auth.ts, and packages/lib/auth/steward-client.ts for the current variable names and verification pathOpenAI or OpenRouter (at least one)
Cloudflare R2 (cloudflare.com)
Fal.ai (fal.ai)
Hetzner Cloud (for container deployments)
packages/cloud-services/container-control-planepackages/cloud-infra/cloud/RAILWAY.md and the container-control-plane
README for the active deployment topologyStripe (stripe.com)
cd cloud
bun install
Copy the example environment file:
cp .env.example .env.local
Edit .env.local with your credentials (see .env.example for all options).
Live .env and .env.local files are ignored. The API dev script also writes an
ignored apps/api/.dev.vars for Wrangler, using real values from .env /
.env.local, dropping placeholder values copied from .env.example, and
generating local-only JWT signing keys when they are missing. Keep real private
keys out of tracked files; apps/api/.dev.vars.example is only a safe template.
Local development runs entirely in TypeScript — no Docker for the cache or
database. The cache client uses embedded Wadis
(WASM Redis) when CACHE_BACKEND=wadis; the database client uses embedded
PGlite when DATABASE_URL starts with pglite://. Cloud
deployments use Upstash REST (KV_REST_API_URL + KV_REST_API_TOKEN) for Redis
and Neon (DATABASE_URL) for Postgres — set those secrets via
wrangler secret put (or bun run cf:secrets:put:prod).
Two local modes:
.env.local): bun run dev. The wrangler API connects to your Neon URL
and Upstash REST. Migrations and scripts apply to whichever DB the URL points
at.DATABASE_URL=pglite://./.eliza/.pgdata and CACHE_BACKEND=wadis in
.env.local, then bun run dev:full. That runs pglite-server as a sidecar
TCP Postgres so the wrangler Worker can connect. Apply migrations with
bun run db:migrate (works against pglite:// URLs directly via embedded
PGlite).Object storage: prod uses Cloudflare R2 via the BLOB Worker binding. Local
dev defaults to inline-in-Postgres (SQL_HEAVY_PAYLOAD_STORAGE=inline); point
STORAGE_PROVIDER at any S3-compatible endpoint when you need to exercise the
object-storage code path.
Minimum required variables:
# Database
DATABASE_URL=postgresql://user:password@host:5432/database?sslmode=require
# Steward Authentication
STEWARD_API_URL=http://localhost:8787/steward
NEXT_PUBLIC_STEWARD_API_URL=/steward
STEWARD_SESSION_SECRET=replace_with_shared_session_secret
STEWARD_JWT_SECRET=replace_with_shared_session_secret
# AI (OpenRouter is the principal provider)
OPENROUTER_API_KEY=your_openrouter_key
# Optional per-family fallbacks for openai/* and anthropic/* models
OPENAI_API_KEY=sk-your_openai_key
ANTHROPIC_API_KEY=your_anthropic_key
# Optional for Groq-native models (groq/compound, groq/compound-mini)
GROQ_API_KEY=your_groq_key
Eliza App variables (for Telegram, iMessage, Discord, and WhatsApp integrations):
# JWT secret for Eliza App user sessions (required)
ELIZA_APP_JWT_SECRET= # Generate: openssl rand -hex 32
# Telegram (optional)
ELIZA_APP_TELEGRAM_BOT_TOKEN= # From @BotFather
# iMessage / Blooio (optional)
ELIZA_APP_BLOOIO_API_KEY= # From Blooio dashboard
# Discord (optional)
ELIZA_APP_DISCORD_BOT_TOKEN= # Developer Portal → Bot
ELIZA_APP_DISCORD_APPLICATION_ID= # Developer Portal → General Information (also the OAuth2 Client ID)
ELIZA_APP_DISCORD_CLIENT_SECRET= # Developer Portal → OAuth2 → Client Secret
# WhatsApp Business Cloud API (optional — for the public Eliza App bot)
ELIZA_APP_WHATSAPP_ACCESS_TOKEN= # Meta Business Settings → System Users → Generate Token
ELIZA_APP_WHATSAPP_PHONE_NUMBER_ID= # Meta App Dashboard → WhatsApp → API Setup
ELIZA_APP_WHATSAPP_APP_SECRET= # Meta App Dashboard → Settings → Basic → App Secret
ELIZA_APP_WHATSAPP_VERIFY_TOKEN= # Generate: openssl rand -hex 32
ELIZA_APP_WHATSAPP_PHONE_NUMBER= # Display phone number in E.164 format (e.g. +14245074963)
Organization-level WhatsApp (Dashboard > Settings > Connections):
Per-organization WhatsApp credentials are stored in the database via the dashboard UI. Each organization connects their own WhatsApp Business account by entering their Access Token, Phone Number ID, and App Secret in the connections settings. The webhook URL and verify token are auto-generated and displayed after connecting.
See .env.example for the full list of Eliza App environment variables.
Generate secure passwords:
# Generate STEWARD_SESSION_SECRET / STEWARD_JWT_SECRET
openssl rand -base64 32
# Generate CRON_SECRET / ELIZA_APP_JWT_SECRET
openssl rand -hex 32
If you want the full local infrastructure stack, start Postgres and Redis first:
bun run db:local:start
Run migrations to create all tables:
npm run db:migrate
If using Stripe billing:
npm run seed:credit-packs
This creates credit pack products in Stripe.
npm run dev
Visit http://localhost:3000.
# Development
npm run dev # Start dev server with Turbopack
npm run build # Production build with Turbopack
npm start # Start production server
# Database
npm run db:generate # Generate migrations from schema changes
npm run db:migrate # Run pending migrations
npm run db:studio # Open Drizzle Studio
# Code Quality
npm run lint # Run ESLint
npm run lint:fix # Auto-fix ESLint issues
npm run format # Format with Prettier
npm run format:check # Check formatting
npm run check-types # TypeScript type checking
# Utilities
npm run seed:credit-packs # Seed Stripe credit packs
npm run bootstrapper:build # Build container bootstrapper
Tests are split by kind; use the right script for what you want to run:
| Script | Directory | What it runs | Needs |
|---|---|---|---|
bun run test:unit | tests/unit/ | Unit tests (mocked deps, fast) | Env preload only; some skip without DATABASE_URL |
bun run test:integration | tests/integration/ | API/DB/E2E integration tests | DATABASE_URL (+ migrations); some need a running server |
bun run test:runtime | tests/runtime/ | Runtime/factory and perf tests | DATABASE_URL (+ migrations), heavier |
bun run test | test:repo-unit:bulk + special | Two staged unit batches (see package.json for included/excluded files) | Env preload only (same family as test:unit) |
bun run test:playwright | tests/playwright/ | Playwright E2E (optional) | @playwright/test installed |
Env is loaded from .env, .env.local, and .env.test via preload.
These are internal engineering notes, not public API/product references. Use
packages/content and the API Explorer for user-facing docs, live API shapes,
model catalogs, and pricing behavior.
AGENT_PRICING mocks break other Agent modules under Bun, and how the billing cron tests isolate mock.module("@/db/client") contention.settings.anthropicThinkingBudgetTokens (MCP/A2A), env default (ANTHROPIC_COT_BUDGET) and cap (ANTHROPIC_COT_BUDGET_MAX), and why thinking budgets are not request parameters.npm run devapp/, components/, lib//dashboard routesnpm run check-typesdb/schemas/*.ts → npm run db:generate → npm run db:migrateapp/: Routes, API handlers, server actionscomponents/: Reusable React componentslib/: Business logic, database queries, servicesdb/: Database schemas and migrations'use client')⚠️ IMPORTANT: Before deploying to production, complete the security configuration for Steward session auth, API key handling, and third-party provider callbacks used by your deployment.
✅ Content Security Policy (CSP): CSP configured for the frontend/API deployment that:
✅ Security Headers: Multiple layers of protection:
X-Frame-Options: DENY (clickjacking protection)X-Content-Type-Options: nosniff (MIME sniffing protection)Referrer-Policy (referrer information control)X-XSS-Protection (browser XSS protection)Permissions-Policy (browser feature restrictions)Before deploying to production, complete these critical steps:
STEWARD_API_URL=https://your-domain.com/steward
NEXT_PUBLIC_STEWARD_API_URL=/steward
STEWARD_SESSION_SECRET=strong_shared_secret_here
STEWARD_JWT_SECRET=strong_shared_secret_here
# Build and run in production mode
npm run build
npm run start
# Test security headers (in another terminal)
npm run test:security-headers
# Test all authentication flows
# - Login with all methods
# - Wallet connections
# - Transactions
# - Check browser console for CSP violations
Use the deployment checklist in this README plus .env.example,
packages/lib/auth.ts, and packages/lib/auth/steward-client.ts for the
canonical auth variables and verification behavior.
Run the automated security headers test:
npm run test:security-headers
This will verify:
Issue: "Domain not allowed"
Issue: Auth frame or redirect not loading
frame-src/redirect settings include the configured auth provider URLs.Issue: CSP violations
next.config.ts if neededCheck browser console CSP violations and the active auth provider settings for provider-specific troubleshooting.
Location: /dashboard/chat and /app/api/v1/chat/route.ts
Features:
useChat hookUsage:
import { useChat } from "@ai-sdk/react";
const { messages, input, handleSubmit, isLoading } = useChat({
api: "/api/v1/chat",
body: { model: "gpt-4o" },
});
Cost: Token-based and model-specific. See Models API and Billing for current pricing behavior.
Anthropic Messages API (Claude Code): For tools that expect the Anthropic Messages API (e.g. Claude Code), use POST /api/v1/messages with the same request/response shape. Set ANTHROPIC_BASE_URL=https://elizacloud.ai/api/v1 and ANTHROPIC_API_KEY to your Cloud API key so usage goes through Cloud credits instead of a direct Anthropic key. See API docs → Anthropic Messages. Why: single API key and billing for both OpenAI-style and Anthropic-style clients.
Public cloud agents (MCP / A2A) — Anthropic extended thinking: For POST /api/agents/{id}/mcp (chat tool) and POST /api/agents/{id}/a2a (chat), extended thinking uses the character’s settings.anthropicThinkingBudgetTokens when the model is Anthropic (0 = off; omitted = fall back to ANTHROPIC_COT_BUDGET). Optional ANTHROPIC_COT_BUDGET_MAX clamps any effective budget. Why: the agent owner controls cost/quality per agent; MCP/A2A clients cannot pass a thinking budget in the request (untrusted input). See packages/docs/anthropic-cot-budget.md.
Location: /dashboard/image and /app/api/v1/generate-image/route.ts
Features:
API:
POST /api/v1/generate-image
Content-Type: application/json
Authorization: Bearer eliza_your_api_key
{
"prompt": "A serene landscape with mountains and lake at sunset"
}
Cost: Model-specific. See Images API and Billing for current pricing behavior.
Location: /dashboard/video and /app/api/v1/generate-video/route.ts
Features:
API:
POST /api/v1/generate-video
Content-Type: application/json
Authorization: Bearer eliza_your_api_key
{
"prompt": "A cinematic shot of a spaceship flying through stars",
"model": "fal-ai/veo3"
}
Cost: Model-specific. See Video API and Billing for current pricing behavior.
Location: /dashboard/gallery
Features:
R2 Benefits:
Setup:
# 1. Create an R2 bucket in the Cloudflare dashboard
# 2. Bind it as MEDIA_BUCKET in apps/api/wrangler.toml
Location: /dashboard/containers and /app/api/v1/containers/route.ts
Container deployments are managed by the cloud-services/container-control-plane
service running on Hetzner. Docker images are built and pushed to GHCR; the
control plane provisions and supervises per-tenant containers.
Features:
elizaos deploy CLIelizaos containers list|delete|logsAPI:
POST /api/v1/containers
Content-Type: application/json
Authorization: Bearer eliza_your_api_key
{
"name": "my-agent",
"port": 3000,
"max_instances": 1,
"environment_vars": {
"NODE_ENV": "production"
},
"image_uri": "ghcr.io/elizaos/my-project:latest"
}
Docker Image Requirements:
/health endpoint for control-plane health checksSee packages/cloud-services/container-control-plane/README.md for the host-side
implementation and packages/cloud-infra/cloud/RAILWAY.md for the deployment
topology.
Location: /dashboard/chat and lib/eliza/
Features:
AgentRuntime from @elizaos/coreDatabase Schema:
agents: Character definitionsmemories: Conversation historyembeddings: Vector similarity searchrooms: Conversation contextsentities: Users and participantsrelationships: Entity connectionsAPI:
# Create room
POST /api/eliza/rooms
{
"agentId": "uuid",
"name": "Chat Room"
}
# Send message
POST /api/eliza/rooms/{roomId}/messages
{
"content": "Hello, agent!",
"authorId": "user-uuid"
}
Location: /dashboard/character-creator and /app/api/v1/character-assistant/route.ts
Features:
Workflow:
Example:
{
"name": "Alex",
"bio": ["A friendly AI assistant", "Specializes in technical support"],
"adjectives": ["helpful", "knowledgeable", "patient"],
"system": "You are a helpful technical support agent...",
"style": {
"chat": ["Be concise", "Use bullet points"],
"post": ["Be professional", "Include examples"]
},
"plugins": ["@elizaos/plugin-sql", "@elizaos/plugin-openai"]
}
Location: Documented management endpoints that explicitly note API key support
API key authentication is available for the specific endpoints documented in this README (for example: /api/v1/chat, /api/v1/chat/completions, /api/v1/messages, /api/v1/generate-image, /api/v1/generate-video, /api/v1/containers, /api/v1/voice/*, /api/v1/billing/*, /api/v1/models, /api/v1/gallery). Not every /api/v1/ or /api/my-agents/ route supports API keys today, so rely on the documented list, enabling:
Session-based auth only (no API key support yet): GET /api/v1/api-keys, POST /api/v1/api-keys, /api/v1/apps/[id]/deploy, /api/v1/dashboard, /api/my-agents/characters/[id]/track-interaction. API key update/delete/regenerate routes accept API keys; use a different key than the one being changed.
Why API Keys for Management Endpoints?
Traditional SaaS platforms only expose limited APIs. We've enabled API key authentication across these management endpoints because:
/api/v1/voice/ instead of provider-specific paths) allow switching providers without breaking integrationsGeneric Voice API: Voice endpoints use provider-agnostic paths (/api/v1/voice/tts instead of /api/elevenlabs/tts) so your code doesn't need to change if the underlying provider changes. Legacy paths are preserved for backwards compatibility.
Billing Management: Agents and developers can configure auto-top-up settings programmatically, ensuring autonomous agents never stop working due to insufficient credits.
Location: /dashboard/api-keys and /app/api/v1/api-keys/route.ts
Features:
Key Format: eliza_<random_hex_secret> (generated by packages/lib/services/api-keys.ts)
API:
# Create API key
POST /api/v1/api-keys
{
"name": "Production API Key",
"description": "Main production key",
"rate_limit": 10000
}
# Regenerate key
POST /api/v1/api-keys/{id}/regenerate
# Delete key
DELETE /api/v1/api-keys/{id}
Using API Keys:
curl https://your-app.com/api/v1/chat \
-H "Authorization: Bearer eliza_your_key_here" \
-H "Content-Type: application/json" \
-d '{"messages": [{"role": "user", "content": "Hello"}]}'
Location: /dashboard/billing and lib/queries/credits.ts
Features:
Pricing:
Stripe Integration:
Credit Packs (example):
[
{ name: "Starter", credits: 10000, price: 9.99 },
{ name: "Pro", credits: 50000, price: 39.99 },
{ name: "Enterprise", credits: 200000, price: 129.99 },
];
Setup:
Use the Stripe dashboard and current Stripe documentation for detailed Stripe configuration.
…/login?ref=<code> from the dashboard header (Invite) and from the Invite friends card on /dashboard/affiliates. GET /api/v1/referrals returns { code, total_referrals, is_active } and creates a code on first use. POST /api/v1/referrals/apply still applies someone else’s code after login. Why: Referral economics existed in code, but there was no product surface for “my link”; flat JSON and a dedicated card avoid confusing referral URLs (?ref=) with affiliate URLs (?affiliate=).See packages/docs/referrals.md for flow, APIs, UI behavior, and WHYs; packages/docs/affiliate-referral-comparison.md for a side-by-side with affiliates.
launch50 → $50. Codes are defined in the SIGNUP_CODES_JSON env var (JSON object); if unset, defaults to {} (no codes). Why env var: So each environment (staging, prod) can have its own codes without committing them; no config file in the repo.POST /api/signup-code/redeem (session auth only) or during Discord/Telegram signup by passing signup_code in the auth body. Why one per org: Prevents abuse (one shared code = one bonus per org) and keeps "welcome bonus" semantics.GET /api/auth/siwe/nonce → sign message → POST /api/auth/siwe/verify → receive API key. New wallets get an account and initial free credits. Why: Agents and headless clients need a way to sign in and get an API key without a browser.X-Wallet-Address, X-Timestamp, X-Wallet-Signature on each request to authenticate without storing a key. First valid request for an unknown wallet creates the account. Why: Some clients prefer not to store an API key; the wallet proves ownership per request.body.walletAddress. All wallet signup (SIWE, wallet-header, topup) uses the same findOrCreateUserByWalletAddress path (slug, credits, race handling). See Authentication / Wallet API for full reference.Location: /dashboard/analytics and lib/queries/usage.ts
Features:
Metrics Tracked:
Provider Health:
Location: /app/api/mcp/route.ts
Features:
Available MCP Tools:
Using MCP Inspector:
npm run mcp:inspector
Using with Claude Desktop:
Add to your Claude Desktop config:
{
"mcpServers": {
"eliza-cloud": {
"url": "http://localhost:3000/api/mcp",
"transport": {
"type": "streamableHttp"
},
"headers": {
"Authorization": "Bearer eliza_your_api_key_here"
}
}
}
}
db/schemas/*.ts)Core Tables:
organizations: Multi-tenant organization data
users: User accounts linked to organizations
api_keys: API authentication
credit_transactions: Credit ledger
credit_packs: Balance top-up packages
usage_records: Per-request usage tracking
generations: Image/video generation records
containers: Hetzner-backed container deployments managed by
cloud-services/container-control-plane
conversations: Platform-level chat history
conversation_messages: Messages in conversations
user_characters: User-created elizaOS characters
model_pricing: Dynamic pricing per model
provider_health: AI provider status
jobs: Background job queue
Integrated into the main database via @elizaos/plugin-sql schema. These tables are managed by elizaOS core:
Agent Runtime Tables:
agents: Character definitions
memories: Conversation history
embeddings: Vector similarity search
rooms: Conversation contexts
participants: Room membership
entities: Users and participants
relationships: Entity connections
components: ECS-style data
worlds: High-level grouping
tasks: Scheduled agent tasks
cache: Key-value cache
logs: Audit trail
message_servers: Central messaging (future)
channels: Message channels
central_messages: Cross-platform messages
Generate migration:
bun run db:generate
This creates SQL migration files in db/migrations/.
Apply migration:
bun run db:migrate
The platform implements atomic operations to prevent quota bypass:
Example: Container quota enforcement
await db.transaction(async (tx) => {
// 1. Lock organization row
const org = await tx
.select()
.from(organizations)
.where(eq(organizations.id, orgId))
.for("update");
// 2. Count containers while holding lock
const count = await tx
.select()
.from(containers)
.where(eq(containers.organization_id, orgId));
// 3. Check quota
if (count >= maxAllowed) throw new QuotaExceededError();
// 4. Create container
return await tx.insert(containers).values(data);
});
See lib/queries/container-quota.ts for full implementation.
Documented management endpoints support multiple authentication methods:
Authorization: Bearer eliza_your_key or X-API-Key: eliza_your_keyGET /api/auth/siwe/nonce, sign EIP-4361 message, POST /api/auth/siwe/verify to receive an API keyX-Wallet-Address, X-Timestamp, X-Wallet-Signature (per-request signature; first request can create account)Policy and rationale: Some routes accept only a browser session (edge returns session_auth_required if you send an API key). Others accept session or API key depending on the handler. Why: automation should work where it is safe and expected; human-only flows (e.g. certain checkouts, invite accept, promo redeem) stay session-bound to reduce scripted abuse and confusing semantics.
http://localhost:3000https://your-domain.com# Text Chat
POST /api/v1/chat
{
"messages": [{"role": "user", "content": "Hello"}],
"model": "gpt-4o"
}
# Image Generation
POST /api/v1/generate-image
{
"prompt": "A beautiful sunset over mountains"
}
# Video Generation
POST /api/v1/generate-video
{
"prompt": "Cinematic shot of spaceship",
"model": "fal-ai/veo3"
}
# Models API
GET /api/v1/models
# List Media
GET /api/v1/gallery?type=image&limit=50&offset=0
# Response:
{
"items": [...],
"count": 10,
"hasMore": false
}
# List Containers
GET /api/v1/containers
# Create Container
POST /api/v1/containers
{
"name": "my-agent",
"port": 3000,
"ecr_image_uri": "123456789012.dkr.ecr.us-east-1.amazonaws.com/elizaos/my-project:v1.0.0",
"environment_vars": {...},
"cpu": 256,
"memory": 512,
"desired_count": 1
}
# Get Container
GET /api/v1/containers/{id}
# Delete Container
DELETE /api/v1/containers/{id}
# Check Quota
GET /api/v1/containers/quota
# Create Key
POST /api/v1/api-keys
{
"name": "Production",
"rate_limit": 10000
}
# List Keys
GET /api/v1/api-keys
# Regenerate Key
POST /api/v1/api-keys/{id}/regenerate
# Delete Key
DELETE /api/v1/api-keys/{id}
Authenticated proxy from cloud to a Railway-deployed agent's
@elizaos/plugin-workflow HTTP surface. Org ownership is enforced by
elizaSandboxService.findRunningSandbox. Cloud forwards the request to
<agent>/api/workflow/... with a Bearer ELIZA_API_TOKEN header.
# List workflows
curl -H "Authorization: Bearer $ELIZA_CLOUD_API_KEY" \
https://api.elizaos.ai/api/v1/agents/$AGENT_ID/workflows
# Create workflow
curl -X POST -H "Authorization: Bearer $ELIZA_CLOUD_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"my-flow","nodes":[]}' \
https://api.elizaos.ai/api/v1/agents/$AGENT_ID/workflows
# Get / update / delete one workflow
curl -H "Authorization: Bearer $ELIZA_CLOUD_API_KEY" \
https://api.elizaos.ai/api/v1/agents/$AGENT_ID/workflows/$WORKFLOW_ID
curl -X PUT -H "Authorization: Bearer $ELIZA_CLOUD_API_KEY" \
-H "Content-Type: application/json" -d '{"name":"renamed"}' \
https://api.elizaos.ai/api/v1/agents/$AGENT_ID/workflows/$WORKFLOW_ID
curl -X DELETE -H "Authorization: Bearer $ELIZA_CLOUD_API_KEY" \
https://api.elizaos.ai/api/v1/agents/$AGENT_ID/workflows/$WORKFLOW_ID
# Trigger an execution (returns 202 + execution id)
curl -X POST -H "Authorization: Bearer $ELIZA_CLOUD_API_KEY" \
-H "Content-Type: application/json" -d '{"inputs":{}}' \
https://api.elizaos.ai/api/v1/agents/$AGENT_ID/workflows/$WORKFLOW_ID/run
# Poll execution status
curl -H "Authorization: Bearer $ELIZA_CLOUD_API_KEY" \
https://api.elizaos.ai/api/v1/agents/$AGENT_ID/workflows/executions/$EXECUTION_ID
Note: the /run and /executions/:id paths require the agent-side
@elizaos/plugin-workflow to mount its run + executions handlers in
src/plugin-routes.ts. Until then those forwarded calls receive whatever
the agent returns (currently HTTP 404).
# Get Current User
GET /api/v1/user
# Response:
{
"id": "uuid",
"email": "[email protected]",
"name": "John Doe",
"organization": {...},
"credit_balance": 5000
}
# Create Room
POST /api/eliza/rooms
{
"agentId": "uuid",
"name": "Chat"
}
# Get Room Messages
GET /api/eliza/rooms/{roomId}/messages
# Send Message
POST /api/eliza/rooms/{roomId}/messages
{
"content": "Hello!",
"authorId": "user-uuid"
}
Rate limits return:
{
"error": "Rate limit exceeded",
"retryAfter": 3600
}
{
"success": false,
"error": "Error message",
"details": {...}
}
HTTP Status Codes:
400: Bad Request (validation error)401: Unauthorized (missing/invalid auth)403: Forbidden (insufficient permissions)404: Not Found429: Too Many Requests (rate limited)500: Internal Server Error503: Service Unavailable (feature not configured)Deployment runs on Cloudflare. The API is a Worker (apps/api, deployed via Wrangler) and the frontend is a Pages project (apps/frontend/dist). Both are wired up in .github/workflows/cf-deploy.yml:
develop → staging (api-staging.elizacloud.ai + Pages branch develop)main → production (api.elizacloud.ai + Pages production)Required GitHub secrets: CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID. Worker bindings live in apps/api/wrangler.toml.
Migrations run from .github/workflows/deploy-backend.yml against the configured DATABASE_URL. For manual migration:
DATABASE_URL=postgres://prod-url bun run db:migrate
/dashboard/analyticsError: Connection refused or SSL required
Solutions:
DATABASE_URL includes ?sslmode=requireError: Authentication errors or login failures
Solutions:
https://elizacloud.ai)Error: undefined values in runtime
Solutions:
.env.local.env.local (not .env)bun run dev or bun run dev:api so
apps/api/.dev.vars is regenerated for WranglerNEXT_PUBLIC_Error: "Container deployment failed" or "Deployment timeout"
Solutions:
container-control-plane health and credentials (Hetzner API token)GET /api/v1/containers/quotaError: "No image/video was generated" or timeout
Solutions:
FAL_KEY is set correctly/dashboard/analyticsError: Usage not tracking or credits not deducted
Solutions:
credit_transactions table for recordscredit_balance columncalculateCost() is being calledError: Credits not added after purchase
Solutions:
STRIPE_WEBHOOK_SECRET matches Stripe dashboardstripe listen --forward-to localhost:3000/api/stripe/webhook
packages/content and engineering notes in packages/docsSee the LICENSE file in the repository root.
Built with ❤️ for the elizaOS ecosystem