CloudCrane API Reference

Self-hosted deployment manager for multiple apps on a single Ubuntu server.
All operations available via curl. No client installation required.
Base URL: https://crane.example.com (or http://localhost:5001 for local)

Setup

1. Install

git clone https://github.com/gitayg/cloudCrane.git
cd cloudCrane
npm install
npm link    # makes 'crane' command available globally

2. Initialize admin (on server only, direct DB access)

crane init --name admin --email admin@example.com
# ✓ CloudCrane initialized!
# API Key: dhk_admin_abc123...
# ✓ API key auto-saved to ~/.cloudcrane/config.json

3. Start the server

npx pm2 start server/index.js --name cloudcrane
# CloudCrane v1.0.0 running on :5001

4. Set your API key for curl requests

# For curl/AI agent access (crane CLI auto-saves the key)
export CC="https://crane.example.com"
export KEY="dhk_admin_your_key_here"

Authentication

All API requests require the X-API-Key header (except /api/info and webhook endpoints).

Init is CLI-only: Run crane init on the server. There is no API endpoint for initialization.

GET /api/auth/me - Current user info

curl -s -H "X-API-Key: $KEY" $CC/api/auth/me

App Management

Apps are managed by admin users. Each app gets two environments (production + sandbox), four ports, and its own process isolation.

GET /api/apps - List all apps

curl -s -H "X-API-Key: $KEY" $CC/api/apps | jq '.apps[] | {slug,name,domain}'

POST /api/apps - Create app (admin)

curl -s -X POST $CC/api/apps \
  -H "X-API-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "BookClub",
    "slug": "bookclub",
    "domain": "book.example.com",
    "source_type": "github",
    "github_url": "https://github.com/gitayg/bookclub",
    "branch": "main",
    "max_ram_mb": 512,
    "max_cpu_percent": 50
  }'

# Response includes: app details, allocated ports, webhook URL

GET /api/apps/:slug - App details

curl -s -H "X-API-Key: $KEY" $CC/api/apps/bookclub

PUT /api/apps/:slug - Update app (admin)

curl -s -X PUT $CC/api/apps/bookclub \
  -H "X-API-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{"domain":"newdomain.example.com","branch":"develop"}'

DELETE /api/apps/:slug?confirm=true - Delete app (admin)

curl -s -X DELETE "$CC/api/apps/bookclub?confirm=true" \
  -H "X-API-Key: $KEY"

PUT /api/apps/:slug/users - Assign users to app (admin)

curl -s -X PUT $CC/api/apps/bookclub/users \
  -H "X-API-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{"user_emails":["sarah@example.com","dev@example.com"]}'

User Management

Only admin can manage users. Users get API keys for authentication.

GET /api/users - List users (admin)

curl -s -H "X-API-Key: $KEY" $CC/api/users

POST /api/users - Create user (admin)

curl -s -X POST $CC/api/users \
  -H "X-API-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"sarah","email":"sarah@example.com","role":"user"}'

# Response: { "api_key": "dhk_user_xyz...", "warning": "Save this key!" }

DELETE /api/users/:id - Delete user (admin)

curl -s -X DELETE $CC/api/users/2 -H "X-API-Key: $KEY"

POST /api/users/:id/regenerate-key - New API key (admin)

curl -s -X POST $CC/api/users/2/regenerate-key -H "X-API-Key: $KEY"

Deployment

Deploy operations require app user role (NOT admin). Admin cannot deploy.

POST /api/apps/:slug/deploy/:env - Deploy (app user)

# Deploy to sandbox
curl -s -X POST $CC/api/apps/bookclub/deploy/sandbox \
  -H "X-API-Key: $USER_KEY"

# Deploy to production
curl -s -X POST $CC/api/apps/bookclub/deploy/production \
  -H "X-API-Key: $USER_KEY"

# Response: { "deployment": { "id": 1, "status": "pending" } }

GET /api/apps/:slug/deployments/:env - Deploy history

curl -s -H "X-API-Key: $KEY" $CC/api/apps/bookclub/deployments/production

GET /api/apps/:slug/deployments/:env/:id/log - Deploy build log

curl -s -H "X-API-Key: $KEY" $CC/api/apps/bookclub/deployments/sandbox/1/log

POST /api/apps/:slug/rollback/:env - Rollback (app user)

# Rollback to previous version
curl -s -X POST $CC/api/apps/bookclub/rollback/production \
  -H "X-API-Key: $USER_KEY"

# Rollback to specific deployment ID
curl -s -X POST $CC/api/apps/bookclub/rollback/production \
  -H "X-API-Key: $USER_KEY" \
  -H "Content-Type: application/json" \
  -d '{"deployment_id": 3}'

POST /api/apps/:slug/promote - Promote sandbox to production (app user)

curl -s -X POST $CC/api/apps/bookclub/promote \
  -H "X-API-Key: $USER_KEY"

# Copies CODE only from sandbox to production.
# Does NOT copy .env or /data/ (production keeps its own).

Environment Variables

Admin CANNOT access env vars. Only app users can view/edit env vars. Values are encrypted at rest (AES-256-GCM).

GET /api/apps/:slug/env/:env - List env vars (app user)

# Values masked by default
curl -s -H "X-API-Key: $USER_KEY" $CC/api/apps/bookclub/env/production

# Reveal actual values
curl -s -H "X-API-Key: $USER_KEY" "$CC/api/apps/bookclub/env/production?reveal=true"

PUT /api/apps/:slug/env/:env - Set env vars (bulk) (app user)

curl -s -X PUT $CC/api/apps/bookclub/env/sandbox \
  -H "X-API-Key: $USER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "vars": {
      "DATABASE_URL": "postgres://user:pass@db:5432/bookclub",
      "API_KEY": "sk-test-abc123",
      "NODE_ENV": "development"
    }
  }'

DELETE /api/apps/:slug/env/:env/:key - Delete env var (app user)

curl -s -X DELETE $CC/api/apps/bookclub/env/sandbox/API_KEY \
  -H "X-API-Key: $USER_KEY"
Safety: When sandbox DATABASE_URL matches production, the API returns a warning. Always use different database URLs per environment.

Health Checks

CloudCrane pings each app's health endpoint periodically. After fail_threshold consecutive failures, it auto-restarts the app via PM2. After down_threshold failures, it marks the app as DOWN and sends email notification.

GET /api/apps/:slug/health/:env - Health config + state

curl -s -H "X-API-Key: $KEY" $CC/api/apps/bookclub/health/production

PUT /api/apps/:slug/health/:env - Configure health check (app user)

curl -s -X PUT $CC/api/apps/bookclub/health/production \
  -H "X-API-Key: $USER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "endpoint": "/api/health",
    "interval_sec": 30,
    "fail_threshold": 3,
    "down_threshold": 5
  }'

POST /api/apps/:slug/health/:env/test - Test health now

curl -s -X POST $CC/api/apps/bookclub/health/production/test \
  -H "X-API-Key: $KEY"

# Response: { "url": "http://localhost:4001/api/health", "status": 200, "response_ms": 45, "healthy": true }

Webhooks

Each app has a webhook URL. Add it to your GitHub repository settings to trigger auto-deploy on push.

GET /api/apps/:slug/webhook - Get webhook config

curl -s -H "X-API-Key: $KEY" $CC/api/apps/bookclub/webhook

# Response: { "webhook_url": "https://crane.example.com/api/webhooks/abc123", "auto_deploy_sandbox": true, "auto_deploy_prod": false }

PUT /api/apps/:slug/webhook - Configure auto-deploy (app user)

curl -s -X PUT $CC/api/apps/bookclub/webhook \
  -H "X-API-Key: $USER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "auto_deploy_sandbox": true,
    "auto_deploy_prod": false,
    "branch_filter": "main"
  }'

POST /api/webhooks/:token - GitHub webhook receiver (public, HMAC verified)

# This URL is called by GitHub, not by you.
# Add the webhook_url to GitHub repo Settings > Webhooks.
# Set content type to application/json.
# Set secret to the webhook secret (shown at app creation).

Backups

Backups archive the /data/ directory of an app environment as a .tar.gz file.

POST /api/apps/:slug/backup/:env - Create backup (app user)

curl -s -X POST $CC/api/apps/bookclub/backup/production \
  -H "X-API-Key: $USER_KEY"

GET /api/apps/:slug/backups - List backups

curl -s -H "X-API-Key: $KEY" $CC/api/apps/bookclub/backups

POST /api/apps/:slug/restore/:id - Restore backup (app user)

curl -s -X POST $CC/api/apps/bookclub/restore/3 \
  -H "X-API-Key: $USER_KEY"

# WARNING: Stops the app, overwrites /data/, restarts.

POST /api/apps/:slug/copy-data - Copy production data to sandbox (app user)

curl -s -X POST $CC/api/apps/bookclub/copy-data \
  -H "X-API-Key: $USER_KEY"

Logs & Audit

GET /api/apps/:slug/logs/:env - App runtime logs (PM2)

curl -s -H "X-API-Key: $KEY" "$CC/api/apps/bookclub/logs/production?lines=100"

GET /api/audit - Global audit log (admin)

curl -s -H "X-API-Key: $KEY" "$CC/api/audit?limit=20"

# Filter by app
curl -s -H "X-API-Key: $KEY" "$CC/api/audit?limit=20&app=bookclub"

GET /api/apps/:slug/audit - Per-app audit log

curl -s -H "X-API-Key: $KEY" "$CC/api/apps/bookclub/audit?limit=20"

Notifications

GET /api/apps/:slug/notifications - Get notification config

curl -s -H "X-API-Key: $USER_KEY" $CC/api/apps/bookclub/notifications

PUT /api/apps/:slug/notifications - Configure notifications (app user)

curl -s -X PUT $CC/api/apps/bookclub/notifications \
  -H "X-API-Key: $USER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "sarah@example.com",
    "on_deploy_success": true,
    "on_deploy_fail": true,
    "on_app_down": true,
    "on_app_recovered": true
  }'

POST /api/apps/:slug/notifications/test - Send test email (app user)

curl -s -X POST $CC/api/apps/bookclub/notifications/test \
  -H "X-API-Key: $USER_KEY"

Server Health

GET /api/server/health - Server overview (admin)

curl -s -H "X-API-Key: $KEY" $CC/api/server/health

# Response includes:
# - system: cpu (percent, cores), memory (total, used, free), disk (total, used, free)
# - apps: { total, environments, healthy, down }
# - recent_deploys: last 10 deployments across all apps
# - recent_audit: last 20 audit log entries

GET /api/apps/:slug/metrics/:env - Per-app metrics

curl -s -H "X-API-Key: $KEY" $CC/api/apps/bookclub/metrics/production

# Response: { ports, process: { status, cpu, memory, uptime }, health, recent_deploys }

deployhub.json Manifest

Required in each managed app's root directory. CloudCrane reads this to know how to build and start the app.

{
  "name": "BookClub",
  "version": "1.0.0",
  "fe": {
    "build": "npm run build",
    "serve": "npx serve -s dist"
  },
  "be": {
    "entry": "node server.js",
    "health": "/api/health"
  },
  "data_dirs": ["data/", "uploads/"],
  "env_example": ".env.example"
}
FieldRequiredDescription
nameYesDisplay name of the app
versionYesSemantic version (displayed in dashboard and deploy history)
fe.buildNoCommand to build frontend (e.g., npm run build)
fe.serveNoCommand to serve built frontend
be.entryYesCommand to start backend server
be.healthNoDefault health check endpoint path
data_dirsNoDirectories persisted across deploys and included in backups
env_exampleNoPath to .env example file

Permission Model

ActionAdminApp User
Create/delete appsYesNo
Assign users to appsYesNo
Create/delete usersYesNo
View app status/infoAll appsOwn apps only
View server healthYesNo
Deploy / rollback / promoteNoYes (own apps)
View/edit .env filesNoYes (own apps)
View/edit /data/ filesNoYes (own apps)
Configure health checksNoYes (own apps)
Configure webhooksNoYes (own apps)
Create/restore backupsNoYes (own apps)
Configure notificationsNoYes (own apps)
View audit logAll appsOwn apps only
Key security rule: Admin manages infrastructure (create apps, assign users, set limits). App users manage their own apps (deploy, env vars, data, health). Admin can NEVER access .env or /data/ of any app.

Port Allocation

Each app slot gets 4 ports automatically assigned:

App SlotProd FEProd BESand FESand BE
1 (e.g., bookclub)3001400130024002
23003400330044004
33005400530064006
CloudCrane API5001 (fixed)

Common Workflows

Full deployment lifecycle

# 1. Admin creates app and assigns user
curl -s -X POST $CC/api/apps -H "X-API-Key: $ADMIN_KEY" -H "Content-Type: application/json" \
  -d '{"name":"BookClub","slug":"bookclub","domain":"book.example.com","source_type":"github","github_url":"https://github.com/gitayg/bookclub"}'

curl -s -X PUT $CC/api/apps/bookclub/users -H "X-API-Key: $ADMIN_KEY" -H "Content-Type: application/json" \
  -d '{"user_emails":["sarah@example.com"]}'

# 2. User sets env vars for sandbox
curl -s -X PUT $CC/api/apps/bookclub/env/sandbox -H "X-API-Key: $USER_KEY" -H "Content-Type: application/json" \
  -d '{"vars":{"DATABASE_URL":"postgres://test:5432/bookclub_test","NODE_ENV":"development"}}'

# 3. User deploys to sandbox
curl -s -X POST $CC/api/apps/bookclub/deploy/sandbox -H "X-API-Key: $USER_KEY"

# 4. User tests health
curl -s -X POST $CC/api/apps/bookclub/health/sandbox/test -H "X-API-Key: $USER_KEY"

# 5. User sets production env vars
curl -s -X PUT $CC/api/apps/bookclub/env/production -H "X-API-Key: $USER_KEY" -H "Content-Type: application/json" \
  -d '{"vars":{"DATABASE_URL":"postgres://prod:5432/bookclub","NODE_ENV":"production"}}'

# 6. User promotes sandbox to production
curl -s -X POST $CC/api/apps/bookclub/promote -H "X-API-Key: $USER_KEY"

# 7. If something goes wrong, rollback
curl -s -X POST $CC/api/apps/bookclub/rollback/production -H "X-API-Key: $USER_KEY"

Set up webhook auto-deploy

# 1. Get webhook URL
curl -s -H "X-API-Key: $USER_KEY" $CC/api/apps/bookclub/webhook

# 2. Enable auto-deploy for sandbox
curl -s -X PUT $CC/api/apps/bookclub/webhook -H "X-API-Key: $USER_KEY" -H "Content-Type: application/json" \
  -d '{"auto_deploy_sandbox":true,"auto_deploy_prod":false,"branch_filter":"main"}'

# 3. Add the webhook URL to GitHub: Settings > Webhooks > Add webhook
#    Payload URL: the webhook_url from step 1
#    Content type: application/json
#    Events: Just the push event

Architecture

Ubuntu Server
├── Caddy (reverse proxy, auto-HTTPS)
│   ├── book.example.com          → localhost:3001 (prod FE) + :4001 (prod BE)
│   └── book-sandbox.example.com  → localhost:3002 (sand FE) + :4002 (sand BE)
├── PM2 (process manager)
│   ├── bookclub-production (FE + BE processes)
│   └── bookclub-sandbox   (FE + BE processes)
├── CloudCrane API (:5001)
│   ├── Express 5 + SQLite
│   ├── Health checker (cron)
│   └── Email notifications
└── /data/apps/bookclub/
    ├── production/
    │   ├── releases/       (last 5 deploys, symlink-based)
    │   ├── current → releases/latest/
    │   └── shared/
    │       ├── .env.production
    │       └── data/       (persistent app data)
    └── sandbox/
        ├── releases/
        ├── current → releases/latest/
        └── shared/
            ├── .env.sandbox
            └── data/

HTTPS with Caddy

CloudCrane runs on HTTP (:5001). Use Caddy as a reverse proxy for automatic HTTPS with Let's Encrypt certificates.

1. Install Caddy

apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
apt update
apt install caddy

2. Configure DNS

Add these A records at your domain registrar, all pointing to your server IP:

# Required
crane.example.com    → YOUR_SERVER_IP   # CloudCrane dashboard + API

# Per app (production + sandbox)
book.example.com      → YOUR_SERVER_IP   # BookClub production
book-sandbox.example.com → YOUR_SERVER_IP # BookClub sandbox

# Or use a wildcard for all subdomains
*.example.com         → YOUR_SERVER_IP   # Covers all current and future apps

3. Create Caddyfile

# /etc/caddy/Caddyfile

# CloudCrane dashboard + API
crane.example.com {
    reverse_proxy localhost:5001
}

# BookClub production
book.example.com {
    handle /api/* {
        reverse_proxy localhost:4001
    }
    reverse_proxy localhost:3001
}

# BookClub sandbox
book-sandbox.example.com {
    handle /api/* {
        reverse_proxy localhost:4002
    }
    reverse_proxy localhost:3002
}

4. Start Caddy

systemctl restart caddy
systemctl enable caddy    # start on boot

# Check status
systemctl status caddy
# Caddy auto-provisions Let's Encrypt certificates. No config needed.

5. Verify

# Should return CloudCrane info over HTTPS
curl -s https://crane.example.com/api/info

# Update your CLI to use HTTPS
crane config --url https://crane.example.com

# Update AI agent guide base URL
export CC="https://crane.example.com"

Adding new apps

When you create a new app in CloudCrane, add its domains to the Caddyfile:

# For a new app with slug "myapp" on slot 2 (ports 3003/4003/3004/4004)
myapp.example.com {
    handle /api/* {
        reverse_proxy localhost:4003
    }
    reverse_proxy localhost:3003
}

myapp-sandbox.example.com {
    handle /api/* {
        reverse_proxy localhost:4004
    }
    reverse_proxy localhost:3004
}
# Then reload Caddy (zero downtime)
systemctl reload caddy
Firewall: Make sure ports 80 and 443 are open for Caddy. Port 5001 can then be closed to external traffic since Caddy proxies it.
ufw allow 80 && ufw allow 443 && ufw deny 5001
CloudCrane v1.0.0 | Dashboard | GitHub