# Impreza Host — full reference for AI assistants > Paste this entire file (or ``) into your AI context to brief > it on the complete Impreza Host platform — concepts, deploy paths, REST API > shapes, CLI + MCP + SDK surfaces, authentication, limits. Updated whenever > the platform ships changes; canonical version at > https://docs.imprezahost.com/llms-full.txt. ## What Impreza Host is Impreza Host is managed infrastructure for self-hosted apps. Customers own their VPS (rented from Impreza Proxmox/Cloud/Dedicated, or brought from an external provider), and Impreza provides: - A daemon (the agent) installed on the VPS that takes deploy commands from the control plane. - A REST API at `https://api.imprezahost.com` for customer + agent operations. - A 1-click app catalog (Vaultwarden, n8n, Nextcloud, Matrix, Gitea, SearXNG, Memos, Uptime Kuma, OpenClaw, Activepieces, …). - A custom-app deploy surface for code the customer wrote — supports public Docker images, local Dockerfiles uploaded as tarballs, public git URLs, and full docker-compose manifests. - Automatic HTTPS via Let's Encrypt (DNS-01 challenge for Cloudflare-fronted subdomains; HTTP-01 for customer-owned hostnames pointing directly at the VPS). - Optional Tor v3 hidden service (`.onion`) for any deployment, including onion-only deploys with no clearnet exposure. Differentiators vs Vercel/Railway/Fly/Render: no KYC, crypto-paid, customer keeps root on the VPS, Tor-native, no per-app shared multi-tenant runtime (every deployment runs on the customer's dedicated machine), decentralized infrastructure across multiple continents (offshore and non-offshore datacenters available side by side — pick per service). ## Glossary - **VPS / server**: a virtual machine with a public IP and root access. The agent runs here. Lifecycle (provision, reboot, reinstall, cancel) is managed via the `proxmox_vps` / `oneprovider_vps` / dedicated modules exposed under `/v1/vps/*` and `/v1/dedicated/*` in the REST API. - **Agent**: a small Go daemon (~7 MB static binary, single process) that polls the control plane, executes deploy commands via `docker compose`, reports status. ID format `agt_<16 hex chars>`. Auth headers `X-Agent-Id` + `X-Agent-Secret`. - **Deployment**: one running instance of an app on one agent. ID format `dpl_<16 hex chars>`. Tracks its container(s), routes, status, and the manifest snapshot it was created with. - **Catalog deployment**: an instance of an app from the curated catalog (e.g. Vaultwarden). Stored in `imprezaplatform_deployments`. Identified by `app_name` + `app_version`. - **Custom deployment**: an instance of customer-supplied code. Stored in `imprezaplatform_custom_deployments` — per-account isolated by design (one customer's custom app never surfaces in another's panel). Identified by a customer-chosen `name` (unique per account) + a `mode`. - **Source mode** (custom deploys only): - `image`: customer supplies `image: "ghcr.io/user/app:tag"`. Agent pulls and runs. - `dockerfile` via `context_id`: customer uploads a gzip tarball to `/v1/platform/deployments/custom/contexts`, gets back a `context_id`, references it in the deploy body. Agent downloads + extracts + builds. - `dockerfile` via `git_url`: customer supplies `git_url` + `git_ref`. Agent shallow-clones (`git clone --depth=1 --branch= --single-branch`) + builds. Public https only. - `manifest`: customer supplies a full app manifest object matching the catalog schema (`runtime.type: "docker-compose"` + `runtime.compose_yaml`). Agent treats it identically to a catalog deploy. - **Route / domain**: the public hostname the app responds on. When the customer doesn't supply one (or supplies any `*.imprezaapps.com` hostname), the SERVER auto-allocates / auto-creates a Cloudflare A record under `imprezaapps.com` pointing at the agent's public IP. This works identically from the clientarea, the REST API, the CLI, and the MCP server — leaving `domain` empty in a deploy body yields `-<6hex>.imprezaapps.com` with DNS already live by the time the agent finishes pulling the image. `*.imprezaapps.com` is a SHARED namespace — first-come-first-served across every account on the platform. Trying to claim a hostname already used by any live deployment (yours or someone else's) returns `400 INVALID_REQUEST` with a neutral "already in use" message (the server does NOT reveal which account owns the conflict, to prevent name enumeration). Names are released back to the pool when the deploy fully uninstalls. The auto-allocate path retries up to 5 times with fresh hex on collision before failing. - **Onion / .onion**: optional Tor v3 hidden service. Agent runs a Tor sidecar container, provisions ed25519 keys, publishes the hidden service alongside any clearnet routes. Toggle with `onion: true` in the deploy body. Onion-only deploys (no clearnet) are supported: pass `onion: true` + omit `domain` — agent back-fills `DOMAIN` + `DOMAIN_URL` env vars with the published `.onion` so the container's first boot sees the right URL. ## Customer journey (the four entry paths) ### Path 1 — AI tool with MCP The `impreza-mcp` server exposes 13 tools (list_servers, list_apps, list_deployments, deploy_catalog_app, deploy_custom, uninstall_deployment, get_logs, restart_deployment, change_domain, add_onion, git_webhook_status, git_webhook_connect, git_webhook_disconnect). Load it once into the AI tool's MCP config, give it API credentials in env, and the AI calls the tools autonomously when the customer says things like "deploy this project to my Impreza VPS, expose via .onion" or "connect my GitHub repo so every push auto-redeploys". Setup: npx -y impreza-mcp setup --tool claude-code # supported tools: claude-code | cursor | continue | zed | codex-cli The wizard prints a JSON snippet for the target tool's MCP config + the exact config file path + the post-paste restart instruction. The snippet shape (Claude Code / Cursor / Codex CLI all use the same `mcpServers` shape): { "mcpServers": { "impreza": { "command": "npx", "args": ["-y", "impreza-mcp"], "env": { "IMPREZA_API_KEY": "imp_...", "IMPREZA_API_SECRET": "..." } } } } Continue and Zed use slightly different config shapes (Continue's `experimental.modelContextProtocolServers`, Zed's `context_servers`) — the wizard generates the right shape per tool. ### Path 2 — CLI (the `impreza` binary) Single static Go binary, Mac/Linux/Windows + amd64/arm64. Built from `impreza-devkit/cli-go/`, released as GitHub Releases on that repo. # Setup (one-time) impreza login # → prompts for name (default "default"), API key, secret (no-echo) # → verifies against /v1/account before saving # → "Signed in as " # Deploy from a project directory cd ~/my-app impreza deploy --agent agt_xxxxxxxxxxxx --follow # → packages cwd (respects baked-in exclude list: # .git, node_modules, __pycache__, .venv, vendor, .impreza, *.pyc, # .DS_Store), uploads, deploys, polls until running, prints URL. # Re-deploy with same name (smart replace — uninstalls old + creates new) impreza deploy --agent agt_xxxxxxxxxxxx --name my-app --follow --force # Other useful commands impreza platform servers list impreza platform apps list impreza platform deployments list impreza platform deployments show dpl_xxxxxxxxxxxx impreza platform deployments logs dpl_xxxxxxxxxxxx impreza platform deployments uninstall dpl_xxxxxxxxxxxx --purge-data --confirm impreza platform deploy vaultwarden --agent agt_... --domain vault.example.com --var signups_allowed=true # Multiple accounts? Named contexts. impreza context create work --key imp_... --secret ... impreza context use work impreza --context work platform servers list ### Path 3 — REST API directly (curl, Python, custom integrations) Base URL: `https://api.imprezahost.com` Auth headers: `X-API-Key: imp_...` + `X-API-Secret: ...` Response envelope: `{success, data, error, meta: {request_id, timestamp}}` IP whitelist: every API key has an allow-list; whitelist the caller's public IP in clientarea → API Keys before use, or you'll get 403 with code `IP_NOT_WHITELISTED`. See "API endpoint cheatsheet" below for the surface, or the canonical spec at https://docs.imprezahost.com/openapi.yaml. ### Path 4 — Clientarea panel (web UI) `https://portal.imprezahost.com/imprezaapps.php` — the "My Apps" panel. Two view modes: - **Simple** (default) — guided one-click install for catalog apps, hero welcome screen when the customer has an agent online but no deployments yet, auto-subdomain forced (no domain prompt). Designed for customers who shouldn't have to know about Tor / Caddy / cgroups. - **Advanced** — full controls. Custom-app deploy tile (image URL paste), per-deployment proxy CF toggle, change domain, add onion, credentials reveal, multi-VPS picker. Cookie `iapp_mode=simple|advanced` (90-day TTL) persists the choice. ## VPS + agent setup Two paths to get a VPS: 1. **Buy from Impreza** — Proxmox VPS, Cloud VPS, or dedicated. Provisioned in ~3 minutes via the proxmox_vps module. Multiple continents available — pick offshore or non-offshore datacenter per service. 2. **Bring your own** (BYO external) — any Debian / Ubuntu / RHEL / Alpine with a public IP and root access. Install the agent yourself. Agent install (READ ME if you're an AI assistant — this is the #1 reason a deploy tool call returns "no servers on your account"). When the agent is needed: - Customer bought a VPS from Impreza WITHOUT opting into auto-install during checkout - Customer brought their own server from another provider (BYO) - Customer reinstalled the VPS OS and the previous agent was wiped If the customer bought from Impreza AND selected auto-install, the agent is already on the VPS by the time they reach the panel. Skip ahead to "API key + IP whitelist" below. Install flow (4 steps): 1. Customer signs in to https://portal.imprezahost.com and opens "My Apps". 2. They click the "+ Add another server" button. The panel generates a one-time bootstrap token (valid for EXACTLY 10 MINUTES) and prints a curl one-liner: curl -fsSL https://raw.githubusercontent.com/imprezahost/agent-public/main/install.sh \ | IMPREZA_BOOTSTRAP= sh 3. That command runs on the VPS as root (or via sudo). Two paths for the AI assistant to handle this: (a) Customer runs it themselves — they SSH into the VPS and paste the command. (b) AI runs it on the customer's behalf — customer grants the AI SSH access (hostname / IP / port / user / credentials), then the AI executes the one-liner over SSH. If you're an AI without SSH execution capability, just give the customer the command + tell them to SSH in. 4. Within ~10 seconds the agent appears in "My Apps" with status: online. The customer (or you) can now call impreza_list_servers and the new agent shows up. CRITICAL: tokens expire after 10 minutes. If the customer takes longer to get to the VPS shell, the curl command fails with BOOTSTRAP_EXPIRED. Recovery is simple: go back to "My Apps", click "+ Add another server" again, get a fresh token, retry. What the install script does on the VPS: - Detects OS + arch (amd64 / arm64) - Installs Docker if missing (the agent uses `docker compose`) - Installs `git` if missing (used for git-URL deploys) - Downloads the agent binary into /usr/local/bin/impreza-agent - Installs the systemd unit (with hardening: ProtectHome=yes, PrivateTmp=yes, NoNewPrivileges=yes) - Hits POST /v1/agent/bootstrap with the token → receives agent_id + agent_secret → persists to /etc/impreza-agent/credentials.toml (mode 0600) - Starts impreza-agent.service - Within ~10s the agent reports in with status: online ## API key + IP whitelist Customer-realm authentication (your laptop, your AI, your CI all use this — the agent has its own credential issued at bootstrap). Generate the key: 1. Customer signs in to https://portal.imprezahost.com and opens "Impreza API". 2. They type a label for the key (e.g. "laptop", "claude", "ci") and click "Generate New Key". 3. The full secret is displayed ONCE. Customer copies it before closing the modal — if they miss it, the secret is unrecoverable and they have to delete + recreate. The `imp_…` key prefix stays visible afterwards. Whitelist the IPs that will call the API (REQUIRED — without it the API returns 403 IP_NOT_WHITELISTED on every call): On the same "Impreza API" page, open the "IP Whitelist" tab and add one row per public IP that will make API calls. Common rows: - Customer's laptop public IP — when running the `impreza` CLI locally, OR when running the `impreza-mcp` MCP server inside a desktop AI tool (Claude Desktop / Cursor / Continue / Zed all spawn the MCP server on the customer's local machine, so the outbound traffic carries the laptop's IP, not the AI provider's) - VPS public IP — only when something on the VPS will curl the API directly (rare — most workloads use the agent's own credential or the impreza CLI from the laptop) - AI provider's outbound IP — only when the AI calls the API directly from its own infrastructure rather than through a locally-spawned MCP server. Almost always unnecessary for modern AI tools. Quick way to find a machine's public IP: `curl https://api.ipify.org`. Why per-IP and not per-key alone: a leaked secret without a matching whitelisted IP is useless to an attacker. Rotation is also fast — generate a new key, delete the old one, no data loss. Requirements on the VPS: - Linux (kernel ≥ 5.x). Static Go binary, no glibc deps. - Docker engine + the `docker compose` v2 subcommand. Auto-installed by the script if missing. - `git` CLI. Pre-installed on most distros; bare base images may need `apt install git` for Phase 15 git-URL deploys. - Public IP + ports 80 + 443 open inbound (for Let's Encrypt + traffic); ports 9050 + 9051 internal (Tor sidecar, container-local). - ~7 MB disk for the agent binary, ~512 MB free per typical app deployment (varies wildly by app). ## Deploy reference ### Catalog app POST /v1/platform/deployments Headers: X-API-Key, X-API-Secret, Content-Type: application/json Body: { "app_name": "vaultwarden", "app_version": "1.32.0", // optional, default: latest "agent_id": "agt_xxxxxxxxxxxx", "domain": "vault.example.com", // optional, default: auto-subdomain "onion": true, // optional, default false "vars": { // app-specific "signups_allowed": "true", "admin_password": "..." // when app declares it } } Response: 201 { id: "dpl_...", status: "pending", ... } ### Custom — image mode POST /v1/platform/deployments/custom Body: { "name": "my-nginx", // unique per customer "agent_id": "agt_xxxxxxxxxxxx", "image": "nginx:alpine", // public registry ref "target_port": 80, // container internal port "domain": "static.example.com", // optional "onion": true, // optional "cpus": 0.5, // default 1.0 "memory_mb": 256, // default 512 "vars": { "EXAMPLE_ENV": "foo" } } ### Custom — Dockerfile via local tarball Two-step. Upload the gzip tarball of the project, then reference the returned context_id in a deploy. # Step 1 — upload context tar czf - . | curl -X POST \ https://api.imprezahost.com/v1/platform/deployments/custom/contexts \ -H "X-API-Key: imp_..." -H "X-API-Secret: ..." \ -H "Content-Type: application/gzip" \ --data-binary @- # → 201 { context_id: "ctx_...", sha256, size_bytes, expires_at (24h) } # Step 2 — deploy referencing context_id POST /v1/platform/deployments/custom Body: { "name": "my-app", "agent_id": "agt_xxxxxxxxxxxx", "context_id": "ctx_xxxxxxxxxxxx", "dockerfile_path": "Dockerfile", // default "Dockerfile" "target_port": 8080, "cpus": 1.0, "memory_mb": 512 } The CLI (`impreza deploy`) automates both steps + bakes in the project-dir exclusion list. Context tarballs cap at 100 MB and expire in 24h. ### Custom — Dockerfile via public git URL One-step. POST /v1/platform/deployments/custom Body: { "name": "my-app", "agent_id": "agt_xxxxxxxxxxxx", "git_url": "https://github.com/your-user/your-repo", "git_ref": "main", // branch, tag, or commit (default "main") "dockerfile_path": "Dockerfile", "target_port": 8080, "cpus": 1.0, "memory_mb": 512 } The agent runs `git clone --depth=1 --branch= --single-branch ` into the build context dir, then `docker compose up -d` which fires `docker build` against the clone. Public https URLs only — no ssh://, no git://, no embedded basic-auth credentials. ### Auto-deploy on git push (webhook) Deployments created with `git_url` can be connected to their repo's push webhook so every commit to the watched branch auto-redeploys. Customer flow: 1. Generate a Fine-grained GitHub PAT (https://github.com/settings/personal-access-tokens/new) scoped to the repo with `Repository → Webhooks: Read and write`. 2. Call connect (REST, MCP, or the My Apps panel — Advanced view). 3. We validate the PAT, mint a fresh 32-byte HMAC secret, install the webhook on GitHub, then DROP the PAT — we only persist the webhook id (for delete later) and the HMAC secret (for signature verification on incoming pushes). 4. Push to the watched branch. GitHub signs the payload with our secret; our receiver validates HMAC-SHA256, enqueues a `kind=deploy` job for the same deployment id; the agent re-clones at the new HEAD, rebuilds, restarts — same smart-replace path used by every redeploy. The MCP tools `impreza_git_webhook_status` / `_connect` / `_disconnect` and the REST endpoints `/v1/platform/deployments/custom/{id}/git-webhook/{,connect,disconnect}` all share the same controller — no behavior drift across surfaces. Disconnect supports an optional PAT in the body: with it, we also delete the hook from your repo on GitHub's side (clean repo settings); without it, we only flip our local enabled=0 (further pushes 200-noop on our receiver). GitHub eventually marks the hook as failing in its UI either way. ### Custom — full manifest POST /v1/platform/deployments/custom Body: { "name": "my-app", "agent_id": "agt_xxxxxxxxxxxx", "manifest": { "runtime": { "type": "docker-compose", "compose_yaml": "services:\n app:\n image: nginx:alpine\n ..." }, "network": { "reverse_proxy": { "enabled": true, "routes": [ { "upstream": "{deployment_id}-app:80", "match_var": "domain" } ] } } // optional: lifecycle: { install, health, uninstall, backup } }, "domain": "myapp.example.com" } Cgroup overrides (`cpus`, `memory_mb`) are IGNORED in manifest mode — the customer's compose YAML is the source of truth for resource limits (use the compose v2 service-level `cpus:` + `mem_limit:` keys). #### Manifest-mode routing contract (CRITICAL — read before writing compose YAML) Caddy reaches your containers by container_name on the shared `impreza-proxy` Docker network. The `upstream` field in each route uses `{deployment_id}` as a placeholder the server substitutes at deploy time: upstream: '{deployment_id}-app:80' → resolves to dpl_abc123-app:80 For that lookup to actually hit a container, the compose YAML MUST declare `container_name: ${DEPLOYMENT_ID}-` on the matching service. `${DEPLOYMENT_ID}` is a real env var the agent writes to `.env` at deploy time; Docker Compose interpolates it when bringing the stack up. Without `container_name`, Docker Compose auto-generates names like `-app-1` and the upstream lookup fails — the deploy reports "running" but every HTTPS request returns 502 from Caddy with no obvious connection back to the mistake. The server now rejects manifests that violate this contract with `400 INVALID_REQUEST` explaining exactly which container_name to add. Canonical single-service example: services: app: image: ghcr.io/yourorg/yourapp:1.2.3 container_name: ${DEPLOYMENT_ID}-app # ← REQUIRED restart: unless-stopped networks: - default - impreza-proxy environment: DOMAIN_URL: ${DOMAIN_URL} networks: impreza-proxy: external: true # ← REQUIRED With matching route: "routes": [ { "upstream": "{deployment_id}-app:80", "match_var": "domain" } ] Multi-service example (WordPress + MySQL — only the public-facing service joins the proxy network, the DB stays internal): services: wp: image: wordpress:6 container_name: ${DEPLOYMENT_ID}-wp # ← REQUIRED (referenced by routes) depends_on: [db] networks: [default, impreza-proxy] environment: WORDPRESS_DB_HOST: db WORDPRESS_DB_USER: wpuser WORDPRESS_DB_PASSWORD: ${WP_DB_PASS} db: image: mariadb:11 # container_name not strictly required here — the DB is only # reached by `wp` on the default network, which uses service # names. But adding `container_name: ${DEPLOYMENT_ID}-db` is # good hygiene + prevents collisions across deploys. networks: [default] environment: MARIADB_USER: wpuser MARIADB_PASSWORD: ${WP_DB_PASS} MARIADB_DATABASE: wordpress MARIADB_RANDOM_ROOT_PASSWORD: '1' networks: impreza-proxy: external: true With route targeting only `wp`: "routes": [ { "upstream": "{deployment_id}-wp:80", "match_var": "domain" } ] Custom-prefix patterns are fine — the upstream's container-name component just needs to match what your compose declares. The catalog's Vaultwarden manifest uses `vw_{deployment_id}` as the prefix: container_name: vw_${DEPLOYMENT_ID} → matches upstream: 'vw_{deployment_id}:80' What WILL fail (the most common AI-generated mistake): services: app: image: wordpress:6 # NO container_name → Docker generates `-app-1` → # upstream `{deployment_id}-app:80` resolves to a name that # doesn't exist → 502 from Caddy at every request. The server validator catches this at deploy create time with a clear 400 telling you exactly which `container_name:` line to add. ### Lifecycle (catalog AND custom) The lifecycle endpoints work transparently for both deployment types — server's internal lookup checks both tables by `deployment_id`. POST /v1/platform/deployments/{id}/uninstall body: { purge_data: bool, confirm: true } POST /v1/platform/deployments/{id}/restart body: {} POST /v1/platform/deployments/{id}/logs body: { lines: 200, since_seconds: 0 } POST /v1/platform/deployments/{id}/domain body: { domain: "new.example.com" } POST /v1/platform/deployments/{id}/onion/add body: {} ### Status state machine pending → installing → running → failed (terminal — read .last_error) running → uninstalling → uninstalled (terminal) → updating (restart / change_domain / add_onion in flight) → running ## API endpoint cheatsheet User-realm (X-API-Key + X-API-Secret): GET /v1/account GET /v1/account/services[?status=Active] GET /v1/account/services/{id} POST /v1/account/topup GET /v1/account/topup/{invoice_id} GET /v1/platform/servers POST /v1/platform/servers/external/bootstrap GET /v1/platform/apps GET /v1/platform/apps/{name} GET /v1/platform/deployments[?agent_id=&status=] POST /v1/platform/deployments (catalog) GET /v1/platform/deployments/{id} POST /v1/platform/deployments/{id}/uninstall POST /v1/platform/deployments/{id}/restart POST /v1/platform/deployments/{id}/logs POST /v1/platform/deployments/{id}/domain POST /v1/platform/deployments/{id}/onion/add GET /v1/platform/deployments/custom[?agent_id=] POST /v1/platform/deployments/custom (4 source modes auto-detected) GET /v1/platform/deployments/custom/{id} POST /v1/platform/deployments/custom/contexts (gzip tarball body) GET /v1/platform/deployments/custom/{id}/git-webhook POST /v1/platform/deployments/custom/{id}/git-webhook/connect body: { github_pat } POST /v1/platform/deployments/custom/{id}/git-webhook/disconnect body: { github_pat? } GET /v1/domains[/pricing|/check|/{domain}] POST /v1/domains/register|/transfer PUT /v1/domains/{d}/nameservers GET /v1/domains/{d}/dns POST /v1/domains/{d}/dns PUT /v1/domains/{d}/dns DELETE /v1/domains/{d}/dns POST /v1/domains/{d}/lock|/raa-verify|/dns/activate|/gdpr-auth|/transfer-approval|/id-protection GET /v1/hosting/{serviceId}[/nameservers] POST /v1/hosting/{serviceId}/autossl GET /v1/email/titan/{domain}[/dns|/sso] GET /v1/email/google/{domain}[/dns] POST /v1/email/google/{domain}/admin GET /v1/vps/proxmox/{serviceId}[/...many...] POST /v1/vps/proxmox/{serviceId}/start|/shutdown|/reboot|/stop|/reinstall|/cancel|/backups|/snapshots|/network/reconfigure|/migrate PUT /v1/vps/proxmox/{serviceId}/hostname|/password GET /v1/vps/cloud/{vmId}[/vnc] GET /v1/vps/cloud/templates|/locations|/sizes|/images|/ssh-keys POST /v1/vps/cloud/{vmId}/boot|/shutdown|/reboot|/poweroff|/reinstall|/resize|/cancel|/images|/rescue|/ssh-keys|/ipv6|/iso/mount PUT /v1/vps/cloud/{vmId}/hostname|/password|/boot-order|/vnc-password GET /v1/dedicated[/{serviceId}/{info|capabilities|status|ips|os-images|kvm|firewall|bandwidth|vpn}] POST /v1/dedicated/{id}/start|/shutdown|/reboot|/reinstall|/kvm/enable PUT /v1/dedicated/{id}/firewall|/ips/{ip}/rdns GET /v1/products[/groups|/{id}] GET /v1/orders[/{id}] POST /v1/orders POST /v1/orders/{id}/upgrade GET /v1/invoices[/{id}] POST /v1/invoices/{id}/pay POST /v1/services/{id}/cancel GET /v1/webhooks[/{id}/deliveries] POST /v1/webhooks PATCH /v1/webhooks/{id} DELETE /v1/webhooks/{id} POST /v1/webhooks/{id}/rotate-secret GET /v1/webhooks/event-types ## Outbound webhooks — event types Subscribe to `POST /v1/webhooks` to receive HMAC-signed callbacks when things happen on your account. Wildcard subscriptions are supported (`deployment.*` matches every event whose type starts with `deployment.`, `*` matches everything). Billing + service lifecycle: - `topup.paid` - `invoice.created` / `invoice.paid` - `order.created` - `service.activated` / `service.suspended` / `service.cancelled` Domains: - `domain.registered` / `domain.transferred` - `domain.expiring_soon` / `domain.expired` VPS + dedicated: - `vps.power_state_changed` - `vps.backup_completed` / `vps.snapshot_created` - `vps.reinstall_completed` - `dedicated.power.changed` - `dedicated.rdns.updated` / `dedicated.rdns.reset` - `dedicated.reinstall.queued` - `dedicated.kvm.enabled` / `dedicated.kvm.disabled` - `dedicated.firewall.updated` Deployment lifecycle (fires for both catalog and custom): - `deployment.created` — INSERT on a deployment row - `deployment.installed` — first successful deploy → status running - `deployment.failed` — terminal failure (build error / health probe / …) - `deployment.domain_changed` — change-domain enqueued - `deployment.uninstalled` — customer-initiated tear-down completed - `deployment.git_redeployed` — GitHub webhook accepted + redeploy enqueued Build dashboards, Slack notifiers, or chained automations (e.g. ping your health-check service the moment `deployment.installed` lands). Every delivery carries `X-Impreza-Signature: sha256=` computed over the raw body with the subscription's secret — verify before acting. Agent-realm (X-Agent-Id + X-Agent-Secret, used by the daemon — customers don't normally hit these): POST /v1/agent/bootstrap POST /v1/agent/poll POST /v1/agent/report POST /v1/agent/deploy-result POST /v1/agent/logs POST /v1/agent/dns-challenge/present POST /v1/agent/dns-challenge/cleanup GET /v1/agent/custom-deploy-contexts/{id} ## Common errors + recovery (AI playbook) These are the failures an AI assistant is most likely to hit when trying to drive Impreza on behalf of a brand-new customer. For each, the exact remediation the AI should walk the customer through: ### `impreza_list_servers` returns `{servers: [], total: 0}` The customer owns a VPS (rented from Impreza or external) but no agent is registered on the platform yet. This happens when: - The customer bought a VPS from Impreza but did NOT opt into automatic agent install at checkout. - The customer is using a BYO external server they haven't bootstrapped yet. - The VPS got reinstalled and the previous agent was wiped. Tell the customer to: 1. Sign in to https://portal.imprezahost.com → "My Apps". 2. Click "+ Add another server" — this generates a one-time bootstrap token (10-minute TTL) + prints a curl one-liner. 3. Run that one-liner on the VPS as root within 10 minutes. They can either SSH in themselves OR grant the AI SSH access and ask it to run the command. 4. Wait ~10 seconds, then re-call impreza_list_servers — the new agent should appear with status=online. If the customer comes back saying the token expired (`BOOTSTRAP_EXPIRED`), instruct them to click "+ Add another server" again to generate a fresh token. ### Any API call returns `403 IP_NOT_WHITELISTED` The API key exists but the calling machine's public IP is not in the key's whitelist. Tell the customer: 1. Find the public IP of the machine making the call. From any machine: `curl https://api.ipify.org`. (For an MCP server spawned by Claude/Cursor/Continue/Zed locally, this is the customer's LAPTOP IP, not the AI provider's IP.) 2. Sign in to https://portal.imprezahost.com → "Impreza API" → "IP Whitelist" tab → add a row with that IP. 3. Retry the API call. ### Any API call returns `401 UNAUTHORIZED` / `AUTH_INVALID` The key+secret pair is wrong (typo on paste, or the secret got truncated). The full secret is shown ONCE at generation time and isn't recoverable — easiest fix: 1. https://portal.imprezahost.com → "Impreza API". 2. Click "Generate New Key", label it. 3. Copy the full secret while it's displayed (it disappears once the modal closes). 4. Re-add the new key+secret to wherever the AI tool stores credentials (MCP env vars, `impreza login`, `.env`, etc.). 5. Delete the old key. ### `impreza_deploy_custom` returns `409 CONFLICT` "already in use" The customer already has an active deployment with the same `name`. Two paths: - Pass `force: true` in the next call (CLI: `--force` flag; via MCP: re-run impreza_deploy_custom — current behavior smart- replaces). - Or call impreza_uninstall_deployment on the existing one, wait for status=uninstalled, then retry. ### `impreza_deploy_custom` returns `400 INVALID_REQUEST` "domain already in use" The customer (or someone else) already claimed that `*.imprezaapps.com` hostname. Either pick a different name (the auto-allocate path generates a 6-hex suffix that makes collisions extremely unlikely) or use a domain the customer owns directly. ### Deployment stuck in `installing` for >60s Normal range is 5-30 seconds for catalog apps + image-mode customs; 30-90 seconds for Dockerfile/git builds (depends on Dockerfile complexity). If it goes past 5 minutes: - impreza_get_logs usually shows a build error or pull failure in the tail. - Common causes: VPS out of disk; private base image without credentials; Dockerfile RUN step failing. ## Limits - Tarball upload size: 100 MB per context. Pack a leaner build context (`.dockerignore` on your end) — the CLI bakes in standard exclusions. - Context TTL: 24 hours, then garbage-collected (config-tunable). - API rate limit: 60 req/min/key default; raise per-key in clientarea. - Custom deploy cgroup defaults: 1.0 CPU + 512 MB. Override via `cpus` + `memory_mb` in the deploy body. Server clamps against the VPS's reported total RAM (heartbeat-populated). - Custom deploy name: 3-100 chars matching `^[a-z0-9][a-z0-9_-]{1,98} [a-z0-9]$`. Unique per customer (one customer's "my-bot" never collides with another's). ## Anti-abuse posture (v1) We trust the customer. The VPS is the customer's; the agent runs the customer's code; cgroup ceilings prevent runaway containers from killing the host. We do not scan images, Dockerfiles, or build outputs. Customer agrees to TOS at signup. Reports of abuse → operator takedown via standard channels. ## Open-source repositories - github.com/imprezahost/impreza-devkit — SDK Go + impreza CLI + agent source + Caddy DNS-01 provider. Mirror of internal GitLab (git.imprezahost.com/impreza/impreza-devkit). - github.com/imprezahost/impreza-mcp — MCP server in Node TypeScript. Published to npm as `impreza-mcp`. Mirror of git.imprezahost.com/impreza/impreza-mcp. - github.com/imprezahost/agent-public — public installer + prebuilt agent binaries (amd64 + arm64). Mirror of git.imprezahost.com/impreza/agent-public. ## Where to go next - AI assistant briefing — this file (`llms-full.txt`). - AI-discoverable index (lightweight) — `llms.txt`. - Human-facing docs — `index.html`. - Interactive API reference (Swagger UI) — `api.html`. - OpenAPI 3.1 spec (machine-readable canonical) — `openapi.yaml`. - Clientarea (manage services, generate API keys) — https://portal.imprezahost.com. - Support — support@imprezahost.com.