# 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. ## For AI assistants — start here If you're an AI coding assistant (Claude Code, Cursor, Codex CLI, Continue, Zed), integrate via the **Impreza MCP server** — NOT the Go SDK / devkit (that is the human-developer / CLI path, not the AI path). One-time setup: npx -y impreza-mcp setup --tool claude-code # supported: claude-code | cursor | codex-cli | continue | zed Then make sure the three prerequisites exist — a server, an agent running on it, and an API key whose IP whitelist includes the machine running the MCP server (almost always the customer's laptop). See "VPS + agent setup" and "API key + IP whitelist" below. The full MCP tool list and the other three entry paths (CLI, REST, clientarea) are in "Customer journey" below. ## 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 (WordPress, PrestaShop, n8n, OpenClaw, Vaultwarden, Nextcloud, Matrix, Gitea, SearXNG, Memos, Uptime Kuma, Activepieces, Jellyfin, PhotoPrism, MinIO, Mattermost, Ollama, Open WebUI, RustDesk, Evolution API (self-hosted WhatsApp REST API), plus managed databases PostgreSQL / MySQL / MariaDB / MongoDB / Redis, …). - A custom-app deploy surface for code the customer wrote — supports public Docker images, local Dockerfiles uploaded as tarballs, git URLs (public or private), 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). ## Products (the hosting lineup) The servers the platform runs on — rent one from Impreza or bring your own: - **Offshore Dedicated Servers** (Ukraine, Romania, Russia, Iceland, Finland) — bare-metal for content freedom without DMCA takedowns, 100% under the customer's control. From $130/mo. https://impreza.host/services/offshore-dedicated-servers/ - **Offshore VPS Hosting** (same offshore jurisdictions) — DMCA-free, for smaller projects. From $17/mo. https://impreza.host/services/offshore-vps-servers/ - **Dedicated Servers** (16+ countries: Australia, Brazil, Bulgaria, Canada, Czechia, Dubai, Finland, Hong Kong, Japan, Moldova, Netherlands, Norway, Philippines, Singapore, Sweden, Switzerland) — 99.9% uptime, full root + BGP + IPMI. From $90/mo. https://impreza.host/services/dedicated-servers/ - **Private VPS Web Hosting** (Netherlands, Switzerland, Hong Kong, Lithuania) — anonymous, instant deploy. From $17/mo. https://impreza.host/services/private-vps-web-hosting/ Payment is anonymous (Bitcoin / Monero), no KYC. Any of these — or a BYO server from another provider — runs the Impreza agent for the app platform described throughout this file. ## 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`. - **Control panel (panel-mode server)**: a server that runs a classic hosting control panel (aaPanel / CyberPanel / CloudPanel / cPanel-WHM) INSTEAD of the agent — picked at checkout. Exclusive mode: no agent, no Docker catalog; the panel owns the box. Not deployable via API/CLI/MCP. See the "Control panels" section. - **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, or private via `git_auth_method` (`deploy_key` = SSH key, any host; `pat` = HTTPS token, GitHub/GitLab). - `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. - **Network mode**: every catalog manifest declares `network.mode`. Three values; `http` is the default and covers every shipped app except the ones explicitly listed below. - `http` (default): app speaks HTTP. Behind Caddy + Let's Encrypt at a `-<6hex>.imprezaapps.com` subdomain (or a customer domain). Every Vaultwarden / WordPress / Nextcloud / Matrix / Gitea / etc. deploy is `http`. The deployment row's `domain` field is the reachability info. - `host_ports`: app speaks raw TCP/UDP on fixed ports — no Caddy, no cert, no domain. The deployment row carries `network_mode: "host_ports"` + `connection_info: { ip, ports: [{port, protocol, label}], public_key }` and clients connect to `:` with the app's own protocol. **RustDesk** (self-hosted TeamViewer / AnyDesk) is the first such app: ports 21115/tcp, 21116/tcp+udp, 21117/tcp, 21118/tcp, 21119/tcp. The server auto-captures the container's boot-time public key into `connection_info.public_key` so the customer pastes IP + key into any RustDesk client and connects with zero post-install steps. - `hybrid`: raw protocol ports AND an HTTP admin UI behind Caddy on a subdomain. Use for apps like AdGuard that publish DNS on `:53/udp` AND a `/admin` web UI. The deployment row carries BOTH a `domain` (for the HTTP UI) and `connection_info` (for the raw ports). Port-conflict guard: fixed protocol ports can only be bound once per host, so a second `host_ports`/`hybrid` deploy on the same agent that reuses a port returns `409 CONFLICT` with a clear message. Uninstall the first one or pick a different server. - **Managed databases** (`host_ports` cohort): PostgreSQL (`postgres:17`, :5432), MySQL (`mysql:8`, :3306), MariaDB (`mariadb:11`, :3306), MongoDB (`mongo:7`, :27017), Redis (`redis:7`, :6379). Deployed like any catalog app via `POST /v1/platform/deployments` with `app_name`. They bind their standard protocol port on the VPS public IP and the deployment row's `connection_info: { ip, ports }` is the reachability info — connect with the native client (`psql`, `mysql`, `mongosh`, `redis-cli`) to `:`. Credentials are the deployment's `ADMIN_USER` + `ADMIN_PASSWORD` vars: a strong 20-char `admin_password` is auto-generated unless you pass your own via REST/CLI/MCP, and `admin_user` defaults to `postgres` (PostgreSQL) or `root` (MySQL/MariaDB/MongoDB) — Redis is password-only (no user). Both surface on the My Apps card (click-to-reveal). Each binds a fixed port so it's one-per-server (MySQL + MariaDB both want 3306 → can't coexist on one agent; the conflict guard returns 409). They ship OPEN to the internet, protected by the strong password; an optional per-IP firewall allowlist (server-level trusted IPs ∪ per-deployment allowed IPs) is on the roadmap and will apply without redeploy. ## 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 a VPS from Impreza** — Proxmox VPS or Cloud VPS. Provisioned in ~3 minutes via the proxmox_vps module. Multiple continents available — pick offshore or non-offshore datacenter per service. 2. **Buy a dedicated server from Impreza** — bare-metal hardware. Manual provisioning (24–48h) while the team racks the box and installs the Impreza agent on the customer's behalf. By the time the server shows up in My Apps the agent is already there as if it were a VPS. 3. **Bring your own** (BYO external) — any Debian / Ubuntu / RHEL / Alpine with a public IP and root access. Install the agent yourself. Checkout-time picker (READ ME if you're advising a customer pre-purchase): the VPS + dedicated cart pages both show a tile picker with the same options. All of them install the Impreza agent; the difference is what (if anything) gets installed alongside: - "Impreza Agent" (recommended for power users) — just the agent. Use when the customer plans to deploy their own Docker images / git repos / catalog apps via the panel + CLI + API later. - "Agent + MCP for AI tools" (recommended for AI-first workflows) — same as above, but the post-provision email includes a 3-step MCP quickstart so Claude / Cursor / Codex / Continue / Zed can deploy directly to the server via tool calls. - One of the catalog apps (Vaultwarden, n8n, Nextcloud, Gitea, WordPress, PrestaShop, RustDesk, …) — agent PLUS that specific app, auto-installed. Fastest path when the customer already knows which app they want. HTTP apps land at a free imprezaapps.com subdomain with HTTPS; non-HTTP apps (RustDesk, future game/VPN/ DNS) land at : with the connection info surfaced on the My Apps card (see "Non-HTTP apps" below). - "No app" — bare server, agent NOT installed. Customer can install it later via the "Custom App" tile in My Apps (see Agent install flow below). 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 and picked "No app" at checkout - Customer brought their own server from another provider (BYO) - Customer reinstalled the VPS OS and the previous agent was wiped - (Dedicated servers don't hit this path — the team installs the agent during manual provisioning, so the customer never sees the bootstrap flow) If the customer bought a VPS from Impreza AND picked "Impreza Agent" / "Agent + MCP" / a catalog app at checkout, 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 either: - the "Custom App" tile at the top of the Marketplace (only shown when the customer has a server with no online agent — disappears the moment any agent comes online), OR - the "+ Add another server" button. Both trigger the same flow. 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 ## Control panels (an alternative to the agent) A VPS or dedicated server can run a classic hosting control panel INSTEAD of the Impreza agent. It's picked at checkout (the same cart tile picker that offers agent / agent+MCP / catalog app / bare) and is an EXCLUSIVE mode: the panel takes over the whole box, so the server runs NO Impreza agent and the Docker app catalog is disabled on it. The customer manages sites, databases, email, DNS and SSL from the panel's own web UI — not via the agent / CLI / API / MCP. Available panels: - aaPanel — free, lightweight. Optional one-click web stack (nginx + PHP 8.4 + MariaDB) at install time, or add components later from the panel's software store. - CyberPanel — free, OpenLiteSpeed-based (LSCache, one-click WordPress, PHP, MariaDB, DNS, email, SSL). - CloudPanel — free, modern nginx panel for PHP / Node.js / Python sites with free SSL. - cPanel/WHM — LICENSE NOT INCLUDED. We install it unlicensed; the customer logs into WHM as the server's root user and either starts the free 15-day cPanel trial or applies their own cPanel license (bought separately, bound to the server's public IP). Recover an expired box over SSH with `/usr/local/cpanel/cpkeyclt`. OS support: each panel requires a supported, freshly-installed OS. The cart validates the server's OS against the panel's matrix and, if it's not compatible, tells the customer to reinstall the server with a supported OS first (Debian/Ubuntu for CloudPanel; broader for aaPanel/ CyberPanel; AlmaLinux 8/9/10 or Ubuntu 24.04 for cPanel). Provisioning + access: - VPS: the panel auto-installs a few minutes after payment (aaPanel's full compiled stack takes longer). No action needed. - Dedicated: the team provisions it once the hardware is ready (dedicated boxes can take up to ~72h depending on country). - When ready, the panel shows up on the My Apps page with its login URL + credentials: username + a click-to-reveal password. For cPanel/WHM there is NO separate password — login is the server's root account. There is NO public REST/CLI/MCP endpoint for provisioning a panel — it's a checkout + clientarea feature, not an API-driven deploy. (The underlying install runs the panel's official installer on the box and reports credentials back to the control plane, but that path is internal.) ## 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", ... } Deploy the same catalog app multiple times on one agent — the platform auto-assigns each instance its own host port, so they coexist without conflict. (Fixed-protocol apps like RustDesk are one-per-server.) ### 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 git URL (public or private) 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. Private repos: set `git_auth_method`. `deploy_key` uses an SSH URL (`git@host:owner/repo.git`) — the create response returns `git_auth.public_key`; add it to the repo as a read-only Deploy Key (GitHub / GitLab / Gitea) or Access Key (Bitbucket) and the agent clones over SSH. Works with any host that supports SSH deploy keys — GitHub, GitLab, Bitbucket, Gitea or self-hosted. `pat` keeps the https URL and takes a repo-scoped read token in `git_pat` (a GitHub fine-grained/classic PAT or a GitLab access token), for GitHub or GitLab; for Bitbucket use `deploy_key`. The credential is encrypted at rest, fetched by the agent just-in-time at clone time, and never returned. Public repos need no auth. ### 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. Works with ANY provider against one per-deployment secret — two ways to connect: GitHub (one-click): 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 secret, install the webhook on GitHub, then DROP the PAT — we persist only the webhook id (for delete later) and the secret (for signature verification). Any other provider — GitLab, Bitbucket, Gitea, self-hosted, CI (manual): 1. Call connect with NO `github_pat` (empty body). 2. We mint the secret and return `{ payload_url, webhook_token }` (shown once). No provider API is touched. 3. Add a push webhook in your repo pointing at `payload_url`, sending the token as the GitLab "Secret token", or as the `X-Impreza-Token` header / `?token=…` query param (Bitbucket / Gitea / self-hosted / CI). On push, the receiver authenticates the payload against the secret — accepting GitHub's `X-Hub-Signature-256` HMAC, GitLab's `X-Gitlab-Token`, or the generic shared token — then enqueues a `kind=deploy` job for the same deployment id; the agent re-clones at the new HEAD, rebuilds, restarts (the smart-replace path every redeploy uses). When the body carries a `ref` we filter on the watched branch; a bare CI trigger with no body just redeploys that branch. 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.