Install
openclaw skills install ezyhostDeploy, manage, and monitor static websites via the EzyHost API. Upload files, build with AI, track analytics, manage custom domains, QR codes, email capture...
openclaw skills install ezyhostThis document describes how AI agents can interact with EzyHost programmatically. For human users, visit ezyhost.io.
https://ezyhost.io/api
All API requests require an API key passed as a header:
x-api-key: $EZYHOST_API_KEY
The key is loaded from the EZYHOST_API_KEY environment variable. Generate your API key at https://ezyhost.io/dashboard/api-keys.
Note: API access requires the Pro plan or higher.
GET /api/projects
Returns { projects: [{ id, name, slug, subdomain, customDomain, status, storageUsed, seoScore, deployedAt, _count: { files, analytics } }] }
POST /api/projects
Content-Type: application/json
Body: { "name": "my-site", "subdomain": "my-site" }
Returns { project: { id, name, subdomain, s3Prefix, url, ... } }
The subdomain must be 3+ characters, lowercase alphanumeric with hyphens. Returns 409 SUBDOMAIN_TAKEN if already in use. Your site will be live at https://{subdomain}.ezyhost.site.
Optional fields: displayMode ("standard" | "presentation"), hideFromSearch (boolean), password (string).
GET /api/projects/check-subdomain/:subdomain
Returns { available: true/false, subdomain }. Use this to validate before creating.
GET /api/projects/:id
Returns { project: { id, name, subdomain, customDomain, status, storageUsed, seoScore, files: [...], ... } }
PATCH /api/projects/:id
Content-Type: application/json
Body: { "name": "new-name", "metaTitle": "...", "metaDescription": "...", "password": "...", "removeBranding": true, "displayMode": "standard", "hideFromSearch": false, "emailCapture": true }
DELETE /api/projects/:id
Deletes the project and all associated files from storage. This cannot be undone.
POST /api/upload/:projectId
Content-Type: multipart/form-data
Body: files (multipart)
Returns { message, filesUploaded, totalSize, project: { id, subdomain } }
Supports .zip archives (auto-extracted) and individual files. All uploaded files go through malware scanning (ClamAV + magic byte validation).
Supported file types: HTML, CSS, JS, JSON, XML, SVG, images (PNG, JPG, GIF, WebP, AVIF, ICO), PDFs, presentations (PPTX), documents (DOCX, XLSX), audio (MP3, WAV, OGG, FLAC, AAC), video (MP4, WebM, MOV), fonts (WOFF, WOFF2, TTF, OTF, EOT), archives (ZIP), 3D models (GLB, GLTF, OBJ), and any other static asset.
Blocked file types: Executables (.exe, .dll, .bat, .sh, .php, .asp, .jar) and SVGs containing scripts or event handlers are rejected.
DELETE /api/upload/:projectId/files/:fileId
POST /api/upload/:projectId/files/bulk-delete
Content-Type: application/json
Body: { "fileIds": ["id1", "id2", "id3"] }
PATCH /api/upload/:projectId/files/:fileId
Content-Type: application/json
Body: { "newPath": "assets/renamed-file.png" }
POST /api/github/:projectId
Content-Type: application/json
Body: { "owner": "username", "repo": "repo-name", "branch": "main", "subfolder": "dist" }
Returns { message, repo, branch, filesUploaded, filesSkipped, totalSize }
Imports files from a public GitHub repository. The branch defaults to "main" and automatically falls back to "master" if not found. The optional subfolder field lets you import only a subdirectory (e.g. "dist", "build").
Requires Pro plan or higher.
GET /api/versions/:projectId
Returns { versions: [{ id, version, label, fileCount, totalSize, createdAt }] }
POST /api/versions/:projectId
Content-Type: application/json
Body: { "label": "v1.0" }
Snapshots the current state of all project files. Requires Pro plan or higher.
POST /api/versions/:projectId/rollback/:version
Restores all files to the state captured in the specified version number.
GET /api/seo/:projectId
Returns { score, suggestions: [{ id, type, severity, message, resolved }] }
Requires Pro plan or higher.
POST /api/seo/:projectId/analyze
Triggers a fresh SEO scan and returns updated suggestions.
PATCH /api/seo/suggestion/:id/resolve
POST /api/seo/:projectId/ai-fix
Content-Type: application/json
Body: { "suggestionIds": ["id1", "id2"] }
Uses AI to automatically fix SEO issues in your HTML files. Counts against AI generation limits.
GET /api/analytics/:projectId?period=7d
Periods: 24h, 7d, 30d, 90d
Basic analytics (all plans):
Returns { totalVisits, visitsByDay, topPages, isAdvanced }
Advanced analytics (Pro+ plans):
Additionally returns { uniqueVisitors, uniqueByDay, topReferrers, topCountries, devices, browsers }
Free plan users receive isAdvanced: false and advanced fields are empty.
POST /api/analytics/track
Content-Type: application/json
Body: { "projectId": "...", "path": "/page", "referrer": "..." }
POST /api/qrcode/:projectId
Returns { qrCodeUrl: "data:image/png;base64,...", siteUrl: "https://..." }
Generates a QR code pointing to the project's live URL (subdomain or custom domain). Returns a base64 data URL. Available on all paid plans.
GET /api/qrcode/:projectId
Returns { qrCodeUrl: "data:image/..." | null }
PATCH /api/emails/:projectId/toggle
Returns { emailCapture: true/false }. Requires Business plan.
When enabled, visitors to the hosted site see a popup email collection form after 5 seconds.
POST /api/emails/submit
Content-Type: application/json
Body: { "email": "visitor@example.com", "projectId": "...", "source": "modal" }
Source options: modal, inline, api. Only works if the project has email capture enabled and the project owner has a Business plan.
GET /api/emails/:projectId?page=1&limit=50
Returns { emails: [{ id, email, source, createdAt }], total, page, totalPages }
Requires Business plan.
GET /api/emails/:projectId/export
Returns a CSV file download. Requires Business plan.
DELETE /api/emails/:projectId/:emailId
GET /api/teams
Returns { teams: [{ id, name, ownerId, members: [...] }] }
POST /api/teams
Content-Type: application/json
Body: { "name": "My Team" }
Requires Business plan.
POST /api/teams/:teamId/members
Content-Type: application/json
Body: { "email": "user@example.com", "role": "editor" }
Roles: editor, viewer.
PATCH /api/teams/:teamId/members/:memberId
Content-Type: application/json
Body: { "role": "viewer" }
DELETE /api/teams/:teamId/members/:memberId
POST /api/teams/:teamId/leave
DELETE /api/teams/:teamId
POST /api/domains/:projectId
Content-Type: application/json
Body: { "domain": "example.com" }
Returns { dnsInstructions: { type, name, value } } — DNS records you need to create.
Requires Pro plan or higher.
GET /api/domains/:projectId/verify
Returns { verified: true/false, dnsInstructions }. Call this after setting up DNS records.
DELETE /api/domains/:projectId
POST /api/aibuilder/chat
Content-Type: application/json
Body: { "message": "build a portfolio site", "history": [], "currentFiles": [] }
Returns SSE stream with events:
status — progress updates ("Generating HTML...")progress — percentage (0-100)done — { files: [{ filename, content }] } — the generated fileserror — { message } on failureCounts against per-plan AI generation limits. Only one AI generation can run at a time per user.
POST /api/aibuilder/deploy/:projectId
Content-Type: application/json
Body: { "files": [{ "filename": "index.html", "content": "<!DOCTYPE html>..." }] }
POST /api/aibuilder/download-zip
Content-Type: application/json
Body: { "files": [{ "filename": "index.html", "content": "..." }] }
Returns a ZIP file download.
GET /api/aibuilder/templates — list saved templates
GET /api/aibuilder/templates/:id — get template details
POST /api/aibuilder/templates — save new template
PATCH /api/aibuilder/templates/:id — update template
DELETE /api/aibuilder/templates/:id — delete template
Template body: { "name": "My Template", "description": "...", "messages": [...], "files": [...] }
GET /api/apikey
Returns { hasKey: true/false, apiKey: "ag_••••..." } — key is partially masked.
POST /api/apikey/generate
Returns { apiKey: "ag_..." } — full key shown only once. Store it securely. Revokes any previous key.
Requires Pro plan or higher.
DELETE /api/apikey
GET /api/plans
No authentication required. Returns { plans: [{ plan, name, priceMonthly, maxProjects, maxStorageMB, ... }] } with all feature flags and limits for each plan.
GET /api/billing
Returns { plan, subscription, aiCredits, usage }.
GET /api/billing/ai-usage
Returns { used, limit, remaining, resetsAt }.
| Feature | Free | Pro ($9/mo) | Business ($29/mo) |
|---|---|---|---|
| Projects | 2 | 15 | Unlimited |
| Storage | 50 MB | 2 GB | 20 GB |
| Max file size | 10 MB | 100 MB | 500 MB |
| Custom domains | — | 3 | Unlimited |
| API access | — | ✅ | ✅ |
| GitHub import | — | ✅ | ✅ |
| Version rollback | — | Up to 5 | Up to 30 |
| Password protection | — | ✅ | ✅ |
| Remove branding | — | ✅ | ✅ |
| SEO tools | — | ✅ | ✅ |
| Advanced analytics | — | ✅ | ✅ |
| QR codes | ✅ | ✅ | ✅ |
| Hide from search | — | ✅ | ✅ |
| Email capture | — | — | ✅ |
| Team members | — | — | Up to 5 |
| Priority support | — | — | ✅ |
| AI generations | 3/mo | 10/mo | 30/mo |
| AI templates | 1 | 10 | Unlimited |
| Presentation mode | — | ✅ | ✅ |
| Basic analytics | ✅ | ✅ | ✅ |
Sites are served at:
https://{subdomain}.ezyhost.sitehttps://{your-domain.com} (Pro+ plans)All sites include HTTPS, CDN caching, automatic file browser for non-HTML projects, and optional presentation mode for PDF/PPTX files.
Injected features on served sites:
removeBranding project setting.All errors return JSON:
{ "error": "Description of the error" }
Plan limit errors include "upgrade": true to indicate a higher plan is needed:
{ "error": "GitHub import requires a Pro plan or higher", "upgrade": true }
Subdomain conflicts:
{ "error": "The subdomain \"my-site\" is already taken.", "code": "SUBDOMAIN_TAKEN" }
Common HTTP status codes:
400 — Bad request / validation error401 — Not authenticated403 — Plan limit reached or feature disabled404 — Resource not found409 — Conflict (subdomain taken)429 — Rate limited500 — Server error# 1. Check subdomain availability
curl https://ezyhost.io/api/projects/check-subdomain/my-site \
-H "x-api-key: $EZYHOST_API_KEY"
# 2. Create a project
curl -X POST https://ezyhost.io/api/projects \
-H "x-api-key: $EZYHOST_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "my-site", "subdomain": "my-site"}'
# 3. Upload files (ZIP)
curl -X POST https://ezyhost.io/api/upload/PROJECT_ID \
-H "x-api-key: $EZYHOST_API_KEY" \
-F "files=@site.zip"
# 4. Generate QR code
curl -X POST https://ezyhost.io/api/qrcode/PROJECT_ID \
-H "x-api-key: $EZYHOST_API_KEY"
# 5. Check SEO (Pro+)
curl https://ezyhost.io/api/seo/PROJECT_ID \
-H "x-api-key: $EZYHOST_API_KEY"
# 6. Add custom domain (Pro+)
curl -X POST https://ezyhost.io/api/domains/PROJECT_ID \
-H "x-api-key: $EZYHOST_API_KEY" \
-H "Content-Type: application/json" \
-d '{"domain": "example.com"}'
Your site is now live at https://my-site.ezyhost.site
# Import a public repo (auto-detects main/master branch)
curl -X POST https://ezyhost.io/api/github/PROJECT_ID \
-H "x-api-key: $EZYHOST_API_KEY" \
-H "Content-Type: application/json" \
-d '{"owner": "emilbaehr", "repo": "automatic-app-landing-page"}'
# 1. Generate a site with AI (SSE stream)
curl -N -X POST https://ezyhost.io/api/aibuilder/chat \
-H "x-api-key: $EZYHOST_API_KEY" \
-H "Content-Type: application/json" \
-d '{"message": "build a modern portfolio with dark theme", "history": []}'
# 2. Deploy the generated files to a project
curl -X POST https://ezyhost.io/api/aibuilder/deploy/PROJECT_ID \
-H "x-api-key: $EZYHOST_API_KEY" \
-H "Content-Type: application/json" \
-d '{"files": [{"filename": "index.html", "content": "..."}]}'