lavela MCP tools
The details behind the lavela MCP tool descriptions — each description links to one section here. What lavela does, and every tool with its cost, is on /info (for agents: /llms.txt). Prices: /pricing.
Connect
Add the server to any MCP client that supports remote servers with browser sign-in (OAuth) — nothing to paste:
https://console.lavela.dev/api/mcp
That URL gives the core tools — the "deploy this repo → operate it" journey: lavela.account_status, lavela.analyze_stack, lavela.launch_saas, lavela.list_projects, lavela.project_overview, lavela.connect_github, lavela.deploy_link, lavela.provision_compute_from_repo, lavela.compute_build_status, lavela.compute_rebuild, lavela.provision_hosting, lavela.get_status, lavela.redeploy, lavela.get_logs, lavela.check_health, lavela.set_compute_secret, lavela.set_build_vars, lavela.env_link, lavela.compute_lifecycle, lavela.compute_always_on, lavela.destroy_compute, lavela.provision_database, lavela.list_resources, lavela.provision_domain, lavela.diagnose_domain, lavela.provision_email, lavela.connect_stripe, lavela.create_checkout, lavela.topup_link, lavela.resume_compute, lavela.delete_project.
For every tool — ads, schedules (cron), preview environments, security scans, legal documents, volumes / object storage, one-off jobs and exec, pushing a private image, and the rest listed under Advanced tools — connect with:
https://console.lavela.dev/api/mcp/all
Sign-in is the same for both URLs (each is its own OAuth protected resource). To switch an existing connection, remove the lavela connector and add it again with the other URL. Claude Code keeps a connection in up to three places (user, local and project), and a leftover one — an old address, or an Authorization header from an old token — stops the browser sign-in from starting, so remove it from all three first (a line for a place with no entry only says so), then add it with the URL you want:
claude mcp remove lavela -s user
claude mcp remove lavela -s local
claude mcp remove lavela -s project
claude mcp add --transport http --scope user lavela https://console.lavela.dev/api/mcp
For every tool, end with this line instead:
claude mcp add --transport http --scope user lavela https://console.lavela.dev/api/mcp/all
npx @lavela/cli connect sets up Claude Code, Cursor, Windsurf and Claude Desktop with the core URL. A static token (Authorization: Bearer mcp_…), created in the console under Settings → Connect your agent → Manage connections → When you need a manual token, works on either URL.
What a connection can do. A browser sign-in, and npx @lavela/cli connect, give the connection every permission except adding funds (projects, provisioning, ops such as ads and security scans, and reading the balance); a manual token has only the permissions the user chose when creating it. lavela handles a request made within those permissions as the user's own request. The user can disconnect an app or revoke a token at any time under Settings → Connect your agent → Manage connections: a revoked token stops working at once, and a disconnected app's current access ends within about an hour.
When a core connection's result names a tool it doesn't have, its next_step.reason starts with "Advanced tool — not in this connection…" — ask the user to reconnect with https://console.lavela.dev/api/mcp/all. Start every new conversation with lavela.account_status. A connected client also lists a guided prompt, launch-my-app ("Launch my app"; in Claude Code type /mcp__lavela__launch-my-app, with the repo URL if known), that walks the first launch step by step.
Deploy from repo
lavela runs an app one of two ways. lavela.analyze_stack says which lane fits the app (its lavela field), and lavela.launch_saas — which creates the project — decides for this account in its deploy field and next_step:
| Lane | Tool | For |
|---|---|---|
| A server built from the repo | lavela.provision_compute_from_repo | An app with its own server (Node, Python, Go, Java, Ruby, Rust, PHP, multi-service), and every app while the account is on the free welcome credit |
| Web hosting | lavela.provision_hosting | A web frontend (static, Vite, Next.js, Astro, SvelteKit, Remix) on a funded account |
A server is paid from the lavela wallet — confirm with the user before starting one (Money). Web hosting needs a funded account (not the welcome credit) but isn't charged to the wallet while it runs. launch_saas is not idempotent: a retry creates a second project, so check lavela.list_projects first.
Building a server from a repo. lavela builds on its own servers from GitHub — the repo's Dockerfile when it has one, otherwise an auto-detected build; the user needs no Docker. The call returns a buildId; builds usually take 3–6 minutes. Poll lavela.compute_build_status (each call waits up to ~20 seconds) until ok or failed and don't start another build meanwhile. Anything not passed gets the default: the repo root, the default branch, port 8080, no env — and a new server is 512 MB. On a redeploy, leaving size (or alwaysOn) out keeps what the running server has; passing it sets it (512mb shrinks a bigger server to 512 MB). Use gitRef for a branch/tag/commit and subdir for a folder of a monorepo. Multi-service: call once per service with a distinct service (web, api, worker) — e.g. the same repo with env {"SERVICE":"api"}.
Reading the repo first. lavela.analyze_stack also says, when it finds them: diskData — signs of a SQLite or file-based database, or of uploads kept on the server's own disk, which is wiped on every redeploy, restart and sleep (ask the user whether that data belongs in a database or on a volume before deploying); and readWarning — the repo could not be read (a wrong URL, or a private repo: connect_github), which is never answered as a static site. An app whose only database is its own Supabase project shows deploy.db: "none" and databaseConnection.migrationRequired: false: lavela keeps that project and creates no database for it, so there is nothing to migrate.
Server sizes. Every account can run the default size, 512 MB (512mb) — the only size on the free welcome credit. After the account's first paid top-up (and once the user has accepted the current Terms, which list the sizes), a bigger size, up to 8 GB (1gb, 2gb, 4gb, 8gb), can be chosen: on a deploy with the size parameter of lavela.provision_compute_from_repo, or in place for a running server with lavela.compute_set_size (an advanced tool — connect with https://console.lavela.dev/api/mcp/all; the server restarts once, no rebuild). A smaller size needs no top-up; before the user accepts the current Terms, only a change back to 512 MB is allowed (CONSENT_REQUIRED otherwise). A bigger size costs more while awake or always on: confirm the price with the user first, quoting a server that sleeps when idle as a range — its asleep price up to its always-on price, the most it can cost a month (Money). A running server's prices are in lavela.project_overview (asleepMonthlyUsd, capMonthlyUsd, with monthToDateUsd and awakeShare for this month so far), every size's on /pricing (and lavela.compute_estimate {size}, advanced — its monthlyUsd is the most the server can cost a month). Before that top-up a bigger size is refused — SIZE_NEEDS_PAID_TOPUP (TRIAL_ROUTE_UNSUPPORTED on the welcome credit): the user adds funds once, then call again. An account that got the welcome credit and whose paid top-ups were all refunded is back on the credit's rules: no bigger size and no growing a server until its next paid top-up (a server already running at a bigger size keeps running; redeploying it then meets the credit's limits). Redeploys and lavela.compute_rebuild keep a server's size; lavela.compute_set_size is the way to change only the size.
Private repos: lavela.connect_github gives a link the user opens once. Code that isn't on GitHub: for a project that already exists, an account with a paid top-up can use lavela.deploy_link — a one-time link (30 minutes, one per service) where the user connects GitHub or drags in the folder; the first call may answer CONSENT_REQUIRED — the user accepts the Terms at the link it gives, then call again. An account with only the welcome credit can't upload a folder (TRIAL_ROUTE_UNSUPPORTED, and a folder dragged in on that page is refused after it is packed): help the user put the code on GitHub first (they can create a repository there), then deploy that repo.
Ports. lavela sets PORT to the port it routes HTTPS to (default 8080). An app that reads $PORT needs nothing. An app that binds a fixed other port needs internalPort — setting only env: { PORT: … } changes the app but not the routing, and every request answers 502.
Environment variables and .env files. An app's variables are of two kinds, set in two places — and the agent never reads a .env value:
- Secrets (API keys,
DATABASE_URL, a Supabase service_role key, tokens):lavela.set_compute_secret— encrypted and write-only (lavela never shows one back), reaching the server when it starts; see Secrets under Redeploy and rollback. - Public build settings (names starting
NEXT_PUBLIC_,VITE_,REACT_APP_,EXPO_PUBLIC_,NUXT_PUBLIC_,GATSBY_,VUE_APP_,NG_APP_,PUBLIC_— the Supabase URL and anon key a web frontend's bundler bakes into the page every visitor downloads):lavela.set_build_vars— readable, never a secret, and used by the next build, so calllavela.compute_rebuildafterwards (confirm first: it replaces the running server). With novarsit returns the current set. An account that doesn't have it yet is refused withBUILD_VARS_UNAVAILABLE: the public values then have to be in the repo (for example.env.production, committed — never a secret key), since a build reads only what the repo commits and whatset_build_varsstored. A page that loads blank usually means a public value is missing.
For the user's whole .env, call lavela.env_link {projectId}: it returns the way that works for this account to send it, with public entries stored as build settings and the rest as secrets — a command for the user to run in the project folder, npx @lavela/cli env push --project <id> (it prints variable names only and the user clicks Allow once in their browser; --dry-run shows the sorting without sending anything), or else a console page where they paste the file. Values travel from the user's computer or browser straight to lavela, never through the agent or the chat. If env_link has no route for the account yet, or for a single secret, set_compute_secret takes a value directly — it then passes through the conversation, so confirm with the user first. A deploy's own env is for non-secret runtime settings.
The disk is not permanent — see Persistent data.
Web hosting details. provision_hosting builds the frontend and adds a database when the app needs one. Build-time env (e.g. NEXT_PUBLIC_SUPABASE_URL + anon key) is stored encrypted on the project and reused by later redeploys — pass only what changes; it may be bundled into the browser, so never put server secrets there. subdir and branch are remembered ("" goes back to the repo root / default branch); a branch name containing "/" must be passed as branch, not as a /tree/… URL. Up to 600 files / 8 MB of build files are sent; optional files (images in public/, docs, tests) may be left out (filesTruncated: true), and a repo whose build files alone are bigger is refused with REPO_TOO_LARGE_FOR_HOSTING — deploy it as a server instead. Other failures:
| Error | What to do |
|---|---|
APP_NEEDS_SERVER | The app runs its own server — deploy it with provision_compute_from_repo |
DB_ENV_MISSING | A Supabase app with no URL / anon key anywhere lavela can see — ask the user (Supabase → Project Settings → API) and pass them as env |
SUBDIR_EMPTY | The folder had no files — pass the right subdir ("" = the repo root) |
GITHUB_TREE_UNAVAILABLE | lavela couldn't read the repo — a private repo needs connect_github; a public one may be rate-limited, redeploy in a few minutes |
HOSTING_DB_QUOTA | The account has no lavela database left for the app (or lavela isn't creating any right now). The error says what it counted: N/M hosted apps with a lavela database counts only the databases made for web apps; N of its M lavela Postgres databases counts all of the account's — web apps', provision_database ones and ones left by deleted projects, which it names with their ids (account_status lists those too). Free one that was counted (list_resources → destroy_resource, with the user's agreement: its data is lost), or redeploy with the user's own DATABASE_URL in env (the console deploy form takes it too) |
MANAGED_POOL_FULL | lavela's database capacity is full right now — redeploy with the user's own DATABASE_URL in env, or try later |
"200 ≠ live." A finished hosting deploy can still serve an error or blank page: get_status shows the hosting module's verified: false with a verifyWarning. Before telling the user the app is live, open the URL or call lavela.check_health (it checks for a real page, not just any HTTP 200).
Nothing watches a server for you. Servers are not monitored, and no alert is sent if one goes down — don't promise the user one. A web-hosting site is checked every 15 minutes, and a change (down, back up) is sent to the project's Slack, Discord or JSON webhook channels (Operations → Notifications), never by email. For a server, call lavela.check_health when the user asks whether the app is up (it wakes a sleeping server, billed as awake time) and read lavela.get_logs.
Build status codes
lavela.compute_build_status returns building, ok (with the live URL) or failed. Its next_step always carries the fix; this table is the reference.
okwith a non-emptyerroris a warning: the server was created but the app didn't answer on its port in time — usually a wrong port or a crash on start. Readlavela.get_logsbefore calling it live. APORT_NOT_RESPONDINGwarning that ends in[detected-port: N]says the app's own files (a Dockerfile'sEXPOSE, a literallisten(N)orport=N, a start script's-p N) show it listens on port N: make it listen on0.0.0.0and the port in$PORT, or deploy again withinternalPortN — lavela never changes the routed port by itself. AnerrorstartingCERT_PENDINGis only informational: the app works at the returned URL and the branded address is still getting its HTTPS certificate (a few minutes).CERT_QUEUEDis informational too: the app works at the returned URL, and lavela issues its branded address later (a weekly limit on new certificates; checked every hour).PRIVATE_SERVERmeans a database or cache server (its port, e.g. 5432 or 6379) was deployed private by design: no public URL (urlis null), and the note names the private address your other servers use while it runs.okwithout a URL is therefore healthy for such a server: the result carriesprivateAddress(<app>.internal:<port>) andserverClass(databaseorcache), also when a readiness warning replaced the note —next_stepthen says to setDATABASE_URL/REDIS_URLon your other server to that address withlavela.set_compute_secret. A database or cache server is always on, and its always-on price is confirmed first: a new one deployed withoutalwaysOn: true, a request that would make one sleep, and a redeploy of one that sleeps are all refused withDATABASE_NEEDS_ALWAYS_ON, which states the prices.- No buildId (a new conversation): pass
projectId(andservice) to get the latest build. rebuildable: truemeanslavela.compute_rebuildcan repeat the build exactly.counted— whether this build counts toward the daily and monthly build limits:truewhile it runs and once its builder started,falsefor a build refused before its builder started and for one whose builder lavela's own build machine failed (BUILDER_OUT_OF_RESOURCES, inside the account's allowance of such failures). An error that says "does not count toward your build limits" always comes withcounted: false.serviceSize,serviceAlwaysOn— the size and always-on setting the service's server runs with; with no server running, the oneslavela.compute_rebuildof that build would create it with (see Redeploy and rollback).
The build ran and failed:
| Code | Meaning | Fix |
|---|---|---|
CONNECT_GITHUB | lavela couldn't clone the repo (usually private) | connect_github, then build again |
SOURCE_FETCH_FAILED | The code couldn't be fetched — wrong repo URL, branch or folder, or an expired upload | Check them with the user; uploaded code → a new deploy_link |
BUILD_PLAN_FAILED | lavela couldn't tell how to start the app. When it could tell why, the error says so and ends in a tag: [cause: monorepo_root] (the repo root is a monorepo — package.json workspaces or pnpm-workspace.yaml — with nothing to start; [folders: …] lists the apps) or [cause: python_no_start] (a Python app with no Procfile; the error gives the exact line) | Add a start command (a start script in package.json; for Python a Procfile line such as web: uvicorn main:app --host 0.0.0.0 --port $PORT or web: gunicorn app:app --bind 0.0.0.0:$PORT, listening on 0.0.0.0 and the port in $PORT) or a Dockerfile, push, then build again. A monorepo root: deploy one app's folder (provision_compute_from_repo with subdir) |
APP_BUILD_FAILED, BUILD_FAILED | The app's own install/compile step failed. Tags: [cause: node_version] (the app needs a newer Node.js than the build used) or [cause: go_toolchain] (go.mod names a Go version the build could not download) | Read get_logs, fix the code (with the user's OK), push, build again. Node: set engines.node in package.json or a .nvmrc; Go: write the three-part release number in go.mod (go 1.25.0) |
BUILD_TIMEOUT | The build ran past its time limit — 30 minutes, or 10 while the account has only the welcome credit (the error says which) — a huge install or a step that never ends (a dev/watch server, a prompt) | Read get_logs; building it unchanged stops at the same place. On the welcome credit a paid top-up of any amount raises the limit to 30 minutes |
BUILD_STALLED, PLATFORM_ERROR | Stopped on lavela's side, not the code | compute_rebuild (same settings); if it fails the same way twice, contact support |
BUILD_REGISTRY_UNAVAILABLE | lavela's image registry was unavailable — lavela's side, nothing charged. counted and its error say whether the build counts toward the build limits: false — "did not start … does not count" (refused before its builder started) — or true — "still counts" (its push failed after it started) | Wait a few minutes, then compute_rebuild (same settings); no build log, nothing in the code to fix |
BUILDER_OUT_OF_RESOURCES | lavela's own build machine ran out of disk space or memory while building — lavela's side, not the app's code, and nothing was charged (builds are included). counted and its error say whether the build counts toward the build limits: false — "does not count" (an account's first few such failures in a day) — or true — "still counts" (past that allowance). The build log has nothing to fix | compute_rebuild (same settings), once; if it stops the same way again, contact support. Don't change the code for it |
SPEND_CAP with counted: true | The account was capped or suspended while the build ran, so its image push was refused. The builder ran: it counts toward the build limits; nothing in the code to fix | account_status says what lifts it, then compute_rebuild |
BUILD_CANCELLED | The project was deleted (or is no longer the account's) while the build ran, so its image push was refused — the owner's own act; nothing deployed, nothing in the code to fix; it counts toward the build limits | Nothing to retry; set the project up again if it is still wanted |
CREDENTIAL_IN_MACHINE_CONFIG | lavela refused to start the builder because its own settings held an infrastructure token — lavela's side, nothing built or charged | Wait a few minutes, then compute_rebuild; if it fails the same way twice, contact support |
NETWORK_UNVERIFIED, NETWORK_MISMATCH, APP_NAME_UNAVAILABLE, NETWORK_CAPACITY_REACHED, SERVICE_OPERATION_STALE | The service registry refused to start the build's server (its network could not be proven or is not the project's own, its name is taken, lavela's network capacity is full, or another change replaced the build's step) — lavela's side, nothing built or charged | Wait a few minutes, then compute_rebuild; if it fails the same way twice, contact support |
The build was refused before it started — the builder never ran, so there is no build log and nothing in the code to fix; the fix is the account's (for SIZE_NOT_OFFERED, the request's):
| Code | Meaning | Fix |
|---|---|---|
TRIAL_SERVER_LIMIT_REACHED | The free trial runs one server at a time | Remove the one no longer needed (project_overview → destroy_compute, or delete_project), or add funds — any top-up lifts every trial limit |
TRIAL_ROUTE_UNSUPPORTED | Not part of the trial (a bigger size, resizing a running server, uploaded code, web hosting, …) | The small size / a GitHub repo, or add funds — and for a closed lane, its way around under When a lane is closed on the welcome credit (Money) |
INSUFFICIENT_WALLET_BALANCE, INSUFFICIENT_AVAILABLE_BALANCE | The balance can't cover a server | topup_link, then build again |
NO_CARD | An older-plan account with no payment method | account_status first (a claimable welcome credit), else billing_setup_card |
SPEND_CAP, CAPPED | Compute is capped or paused | account_status says why and what lifts it |
PAYMENT_EXHAUSTED | A card payment failed (older plan) | The user updates the card |
MACHINE_LIMIT | The project already runs its maximum number of servers | Remove one |
SIZE_NEEDS_PAID_TOPUP | A size above 512 MB (for a new server, or growing one) on an account without a paid top-up — never made one, or back on the welcome credit after they were all refunded | Tell the user the size's monthly price (the message has it); one paid top-up of any amount (topup_link), then call again — or use 512mb |
SIZE_NOT_ON_PLAN | A size above 512 MB (for a new server, or growing one) on an account on the older card plan (not on the prepaid balance) | Don't offer a top-up — the user contacts support@lavela.dev to move plans; meanwhile use 512mb, keep the server's current size, or go smaller |
SIZE_NOT_OFFERED | Not one of lavela's sizes (e.g. cpuKind / cpus / memoryMb that match none) | Call again with size: 512mb, 1gb, 2gb, 4gb, 8gb — or leave it out (a new server is 512 MB, a running one keeps its size) |
MEMORY_CEILING, CPU_CEILING, PERFORMANCE_NOT_ALLOWED | The size is above the account's limit | Deploy again at an allowed size, keeping the other settings |
RATE_LIMIT | Too many servers started in the last hour | Wait, then build again |
WALLET_COMPUTE_DISABLED | lavela paused new servers platform-wide | Try later; support if it persists |
ORG_CAPACITY_REACHED | lavela's servers are at capacity (nothing was started or charged; lavela has been alerted) | Tell the user — nothing in the request or the account changes it; don't retry in a loop |
TRIAL_CAPACITY_FULL | Free trial servers are full; they free up only when trial servers are removed (nothing was charged) | Add funds to deploy now (the welcome credit stays): topup_link, then build again |
ACCOUNT_MACHINE_LIMIT | The account is at its machine limit (servers, jobs, running builds; stopped servers count) | Remove servers no longer needed, or contact support |
REGISTRY_UNAVAILABLE | lavela's image registry is switched off right now — lavela's side; nothing was queued, built, charged or counted (push_image is refused the same way) | Retry the same call in a few minutes; if it keeps failing, contact support |
CREDENTIAL_IN_MACHINE_CONFIG | A Fly API token (FlyV1 …, fm2_…, fo1_…) is in the env or metadata the call supplied (the error names where, never the value) — refused before anything was created, queued or charged | Don't retry as is: store it with set_compute_secret (the server reads it at runtime), then send the call again without it. compute_rebuild takes no env — the token is in that build's saved settings: after storing it, deploy again with provision_compute_from_repo and the env without that key (it replaces the running server) |
DATABASE_NEEDS_ALWAYS_ON | A database or cache server is private, so its address never wakes a sleeping server. Refused before anything is queued, counted or charged (the message states the prices): a new one asked for without alwaysOn: true (scaleToZero: false on an image deploy), a request to make one sleep or to switch its always-on off, a redeploy or rebuild of one that sleeps, or of one whose server was removed while it slept | Confirm the always-on price with the user, then call again with alwaysOn: true. A sleeping one: compute_always_on {alwaysOn: true} first, then rebuild (one whose server was removed: provision_compute_from_repo with alwaysOn: true and the same repo, branch, folder and port). To pay less, stop or delete it — always-on can't be turned off for it |
TRIAL_DATABASE_NEEDS_DISK | A database image whose data lives on a disk (Postgres, MySQL, MongoDB, …) on the free welcome credit, which has no disk — its data would vanish on every restart. Nothing was deployed or charged | A free Supabase or Neon database under the user's own account, its URL set as DATABASE_URL with set_compute_secret; or a paid top-up (topup_link), then provision_database |
TRIAL_CACHE_UNAVAILABLE | A cache image (redis, valkey, memcached) on the free welcome credit: it would be the account's one server, and private (no internet address), so nothing could reach it. Nothing was deployed or charged | A Redis URL from the user's own provider (a free Upstash database) set as REDIS_URL with set_compute_secret; or add funds (topup_link) to lift the one-server limit |
TRIAL_REGION_MISMATCH | A database or cache server asked for a region other than the one the welcome-credit account's servers run in (the error names it). Nothing was deployed or charged | Call again without region (it goes where the others are) or with that region |
DATABASE_IMAGES_UNAVAILABLE | lavela isn't running database or cache images as servers right now. Nothing was deployed or charged | provision_database for Postgres, or a database URL from the user's own provider (Supabase, Neon) set with set_compute_secret; for Redis, a URL from the user's own provider |
NETWORK_MISMATCH | lavela refused to start a database or cache server because its app is not in the owner's own private network (it would not be private) — lavela's side, already reported; nothing was built, deployed or charged | Nothing to change in the request: wait a few minutes, then compute_rebuild (or the same deploy again); if it fails the same way twice, contact support |
ABUSE_HOLD | lavela's own automatic hold of one server (suspected abuse, or the free trial's daily data-transfer limit): every start, restart, redeploy, resize and always-on change of it is refused; the account's other servers are not affected | Nothing the user or the agent does releases it — don't retry any of them. The user contacts support (a person reviews it); the daily-transfer hold lifts by itself at 00:00 UTC |
SERVICE_NAME_INVALID | A new service or resource name breaks lavela's naming rule (a service name: lowercase letters, digits and single dashes, starting with a letter, at most 20 characters; the error says how) — nothing was created | Call again with the name the error suggests, or another valid one |
SERVICE_NAME_TAKEN | The name is already used in this project (the error names the resource), or — for a service — it would share another service's server (service names are compared without their dashes) — nothing was created | Use the existing one (list_resources, project_overview) or call again with another name |
DECLARED_SERVICE_LIMIT | The project (10) or the account (5; 3 on the welcome credit) already has the most services whose server space was created but never deployed | Deploy one, or remove one the user confirms is unused |
ACCOUNT_NOT_FUNDED | Setting variables, pushing an image or adding a disk for a service that is not deployed yet creates server space, which needs funds (or the welcome credit) on the account | topup_link or account_status, then the same call again |
NETWORK_UNVERIFIED | lavela could not confirm which private network the service's server space is in — nothing new was created or changed; running servers are untouched; lavela has been alerted | Wait a few minutes, then the same call; don't retry in a loop |
APP_NAME_UNAVAILABLE | The service's server name and its suffixed form are both taken at lavela's hosting provider — lavela's side, alerted | Contact support; retrying won't help |
NETWORK_BUDGET_REACHED | The account created the most new private networks it may | Delete a project the user no longer uses (its network is reused), then try again |
NETWORK_CAPACITY_REACHED | lavela cannot create another private network right now — lavela's side, alerted | Try later; don't retry in a loop |
SERVICE_BUSY | Another change to the service, or another call creating the same thing, is still running, so this one changed or created nothing; a change that stopped partway needs lavela's attention | Wait a minute (retry_after_ms), then call the same tool again with the same name — it returns the existing resource once it exists; an interrupted attempt frees the name within 15 minutes; for "needs attention", contact support |
SERVICE_IDENTITY_CHANGED | A new server name was saved; no server or build started | Repeat the same request once; funds and limits are checked again |
SERVICE_OPERATION_STALE | This change was superseded and stopped without changing anything | Read the service's state, then repeat only if still wanted |
SERVICE_HAS_RESOURCES | A removal would delete a server, a disk or snapshots — nothing is deleted automatically | Remove them explicitly, with the user's confirmation |
SUSPENDED, DATA_RESIDENCY, REGION_NOT_ALLOWED | Only support can help | Contact support; don't retry |
Redeploy and rollback
A server built from a repo (after the user pushes, or once a failure is fixed): lavela.compute_build_status {projectId, service} returns the service's last build — pass its id to lavela.compute_rebuild. It repeats the build exactly (branch, folder, port, env, volume mounts) and fetches the branch again, so pushed code is included (a build pinned to one commit builds that commit again). The server keeps its current size and always-on setting — a resize or a switch made after the build is never undone. With no server running (it was removed, or lost), the rebuild creates it with the size and always-on setting it last ran; a build started after that server was removed, or for a service that never had one, uses its own — and on the welcome credit a removed server bigger than 512 MB comes back at the build's own size (512 MB when that is what it was built with). The build's serviceSize and serviceAlwaysOn say which. An always-on server pays its always-on price every second, while a sleeping one pays it only while awake (its asleep price otherwise), and a bigger size costs more while awake or always on (this server's prices: project_overview; all: prices) — say so when you confirm the rebuild with the user. To change only the size, lavela.compute_set_size (advanced) resizes the running server in place, no rebuild. Never redeploy with provision_compute_from_repo and guessed settings: whatever it is given replaces the running server — defaults for the rest, except the size and always-on setting, which it keeps when they are left out. To change only the port, the folder or the branch, pass internalPort, subdir or gitRef to the same compute_rebuild call (they are checked like provision_compute_from_repo's; "" for subdir / gitRef means the repo root / the default branch): everything else is repeated as above, the new build stores the change, and every later rebuild of that build repeats it — a failed build keeps it too, so rebuilding that one repeats the change. For env, use set_compute_secret / set_build_vars. When a build can't be repeated:
DATABASE_NEEDS_ALWAYS_ON— the exception for a database or cache server (private; its private address never wakes a sleeping server): a rebuild of one that sleeps is refused before anything is queued or counted, with both prices — confirm the always-on price with the user, switch it on first (lavela.compute_always_on {alwaysOn:true}), then rebuild. One whose server was removed while asleep:provision_compute_from_repowithalwaysOn: trueand the same repository, branch, folder and port (a rebuild never raises it silently). One that is already always on, or whose last server was, rebuilds as before.REBUILD_REQUEST_UNKNOWN— its settings weren't kept: confirm branch, folder, size, port, env and mounts with the user, thenprovision_compute_from_repo.UPLOAD_REBUILD_UNSUPPORTED— it came from an uploaded folder: a newdeploy_linkfor the user to upload again.
A server whose last build says ok but that no longer exists (it was removed) is shown by lavela.project_overview as not_running — redeploy it with compute_rebuild (it comes back as the build's serviceSize and serviceAlwaysOn say — normally the size and always-on setting it last ran; uploaded code: a new deploy_link), never report the old URL as live. A server that exists shows as deployed with live: null: lavela's records can't tell a running server from one stopped by the user or a balance pause — check_health checks it, and compute_lifecycle action start starts a stopped one.
A reclaimed server: when a welcome-credit server's machine was removed (its balance ran out and it stayed stopped, or its owner left it stopped for a long time), lavela.project_overview and lavela.compute_list show it as reclaimed (state) with reclaimedAt, appDeletesAt and its old machineId. lavela keeps its settings, image and variables until appDeletesAt: lavela.compute_lifecycle {machineId, action:'start'} recreates it exactly as it was ("Start again": no build, nothing to repeat; it needs funds and is paid from the wallet) — use it before compute_rebuild, which rebuilds from source and counts as a build. After appDeletesAt the variables and the built image are deleted: a start then rebuilds from the repository (it counts as a build) and the result lists the variables to set again (variablesToSetAgain). lavela.resume_compute only lifts a pause; it never starts or recreates a server.
Web hosting: lavela.redeploy {projectId} rebuilds the same repo, folder and branch with the env stored on the project (CONFLICT = a deploy is already running). To undo a bad deploy instantly, lavela.list_deployments → lavela.rollback {deploymentId} (advanced tools) re-points the live site without a rebuild (web hosting only — confirm with the user; it changes what is live).
Secrets: lavela.set_compute_secret restarts the running server by default (a server at rest gets them at its next start; a one-off job or a build running at that moment keeps its old values); with apply: false the next deploy picks them up. A service name nothing runs under yet starts a new service: the result says createdService: true, and warns SIMILAR_SERVICE next to a close name (wbe for web), so confirm the name with the user. unset: ["NAME", …] removes names (the running server restarts; a name that is not set is a clean no-op that restarts nothing; the same name can't be in secrets and unset in one call). A name is capital letters, digits and underscores (at most 64, not starting with a digit), a value is at most 32 KiB, and a service holds at most 100. PORT, PATH, HOME, HOSTNAME, PRIMARY_REGION, FLY_* and LAVELA_* are set by lavela and the provider and are refused (VARIABLE_RESERVED): a server's port is internalPort on a deploy. A secret value that contains ${{ … }} is refused:
| Error | Meaning | What to do |
|---|---|---|
REFERENCES_UNAVAILABLE | References between services aren't available yet, so lavela would have stored the text literally — nothing was stored (the error names the keys) | Set the real value instead (another service's private address: compute_wire) |
VARIABLE_RESERVED | A secret named like something lavela or the provider sets in every server's environment (PORT, PATH, HOME, HOSTNAME, PRIMARY_REGION, FLY_*, LAVELA_*) — nothing was stored (the error names the keys, never a value) | Use another name; a server's port is internalPort when deploying, not a secret |
Read command audit history (lavela.compute_exec_history, advanced). Pass projectId, optional service (default web) and limit (up to 50). Returns only owned audit times, outcomes, exit codes and bounded byte/duration metadata; no command text, command output or credentials. It never runs a command or wakes a server.
Run a command in a server (lavela.exec_compute, an advanced tool). {projectId, service, cmd} runs a shell line (/bin/sh -c, at most 4 KiB) inside the service's server for up to 60 seconds; argv (the program and its arguments as a list, no shell) is for an image that has no /bin/sh. The result has exitCode, stdout and stderr — at most 64 KiB of each, then truncated: true (stdoutBytes and stderrBytes say how much there was) — and durationMs. It is free, but it only runs in a server that is already running: it never starts or wakes one and never keeps one awake. An account may run 10 commands a minute. On a server with a disk, or a database or cache server, a command can change data on the disk: confirm with the user first — dryRun: true answers (with that warning) without running anything or counting against the limit. lavela never returns a password, but a command run in your server can read its environment, so its output may show one; lavela keeps no command text and no output — each call leaves one audit record with a keyed fingerprint of the command, never the command. An account that only has the welcome credit cannot run commands yet (TRIAL_ROUTE_UNSUPPORTED).
| Error | Meaning | What to do |
|---|---|---|
MACHINE_NOT_RUNNING | The server is stopped, suspended, starting or gone (params.state) — nothing ran | Ask the user, then compute_lifecycle start, and run the command again |
EXEC_NO_SHELL | The image has no /bin/sh — nothing ran | Call again with argv instead of cmd |
EXEC_UNAVAILABLE | lavela could not check the exec limit — its side, already reported; nothing ran | Try again in a few minutes; if it persists, contact support |
RATE_LIMITED | More than 10 commands in a minute (the message names the wait) | Wait retry_after_ms, then run it again |
Conditional service control refusals
Service control features require account availability and verified provider capabilities. A documented refusal does not mean a feature is enabled. No failed verification is treated as permission to expose a port, start a held server, or allocate paid resources.
| Code | Meaning | Fix |
|---|---|---|
INVALID_SERVICE_SETTINGS | Choose valid service settings and a port from 1 to 65535. | Correct the request as described, refresh the owned service, and retry. |
PUBLIC_DATABASE_PORT | Database and cache services must remain private. | Correct the request as described, refresh the owned service, and retry. |
TEMPLATE_PRIVATE_ONLY | This datastore template must remain private. | Correct the request as described, refresh the owned service, and retry. |
PRIVATE_SERVICES_UNAVAILABLE | Private services are unavailable until their isolation and routing requirements are verified. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
PRIVATE_SLEEP_UNSUPPORTED | Keep this private service always on until wake support is verified. | Correct the request as described, refresh the owned service, and retry. |
DATABASE_NEEDS_ALWAYS_ON | Keep database and cache services always on. | Correct the request as described, refresh the owned service, and retry. |
WORKER_ALWAYS_ON | Workers must be private and always on. | Correct the request as described, refresh the owned service, and retry. |
PRIMARY_MUST_BE_PUBLIC | Choose a public service as the primary service. | Correct the request as described, refresh the owned service, and retry. |
SERVICE_SETTING_CONFLICT | The requested service settings conflict. | Correct the request as described, refresh the owned service, and retry. |
SERVICE_ROUTING_ROLLBACK_BLOCKED | This project requires the stored service routing contract. Restore its routing settings before changing compute resources. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
SERVICE_HELD | Start the held service through its authorized start path before changing its configuration. | Ask the owner to resolve or approve the stated requirement before retrying. |
CUSTOM_DOMAIN_ATTACHED | Detach the service custom domain before making the service private. | Ask the owner to resolve or approve the stated requirement before retrying. |
REFERENCE_INVALID | Use the supported service reference grammar and export names. | Correct the request as described, refresh the owned service, and retry. |
REFERENCE_UNRESOLVED | Choose an available producer in this project. | Correct the request as described, refresh the owned service, and retry. |
REFERENCE_SELF | A service cannot reference itself. | Correct the request as described, refresh the owned service, and retry. |
REFERENCES_UNAVAILABLE | Service references are unavailable for this account. | Correct the request as described, refresh the owned service, and retry. |
PROJECT_REGION_MISMATCH | Linked private services must use the project home region. | Correct the request as described, refresh the owned service, and retry. |
PROJECT_REGION_UNVERIFIED | The service region could not be verified. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
LINK_TARGET_UNREACHABLE | Start and verify the producer before linking it. | Correct the request as described, refresh the owned service, and retry. |
LINK_SOURCE_UNAVAILABLE | This producer has no supported, verified exports. | Correct the request as described, refresh the owned service, and retry. |
LINK_CONSUMER_UNSUPPORTED | Choose a compute service as the link consumer. | Correct the request as described, refresh the owned service, and retry. |
EXPORT_SNAPSHOT_MISMATCH | The producer routing changed; publish a verified export version before applying references. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
VARIABLE_CONFLICT | Choose different variable names or explicitly authorize overwrite. | Correct the request as described, refresh the owned service, and retry. |
VARIABLE_LIMIT | Keep the service within the variable count limit. | Correct the request as described, refresh the owned service, and retry. |
VARIABLE_SHADOWS_ENV | Remove the conflicting app environment key before applying the variable. | Correct the request as described, refresh the owned service, and retry. |
TEMPLATE_MANAGED_KEY | This variable is managed by the datastore template. | Correct the request as described, refresh the owned service, and retry. |
VARIABLE_RESERVED | Choose a variable name that is not reserved by the platform. | Correct the request as described, refresh the owned service, and retry. |
INVALID_VARIABLE_INPUT | Use valid variable names and bounded values. | Correct the request as described, refresh the owned service, and retry. |
PROVIDER_UNAVAILABLE | The real compute provider is unavailable. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
PROVIDER_REQUEST_FAILED | The provider request could not be completed. No provider response body is exposed. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
DNS_PROVIDER_UNAVAILABLE | The real DNS provider is unavailable. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
PROVIDER_PROBE_UNAVAILABLE | Verified routing capability for this project network is unavailable. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
ENDPOINT_PROBE_UNAVAILABLE | A probe from the exact project network is required before this change. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
ENDPOINT_UNVERIFIED | The applied service endpoint could not be verified. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
CUSTOM_DOMAIN_PROBE_UNAVAILABLE | The service custom-domain inventory could not be verified. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
SERVICE_PRICE_UNAVAILABLE | The current service price could not be verified. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
DNS_OWNERSHIP_UNVERIFIED | DNS ownership could not be verified; foreign records were preserved. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
IP_ASSIGNMENT_MISMATCH | The provider IP assignment does not match the service network. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
MACHINE_STATE_UNAVAILABLE | The current service machine state could not be verified. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
MACHINE_VERSION_UNVERIFIED | The accepted machine version could not be verified. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
MACHINE_CHANGED | The machine changed during this request; refresh and retry. | Correct the request as described, refresh the owned service, and retry. |
SERVICE_CLASS_MISMATCH | The service machines have incompatible classifications. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
CERTIFICATE_ISSUANCE_PENDING | Certificate issuance is pending; keep the journal for a later retry. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
CERTIFICATE_UNAVAILABLE | The service certificate could not be verified. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
TEMPLATE_EXPORTS_UNAVAILABLE | The current template connection exports could not be verified. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
SERVICE_NAME_CONFLICT | Choose a producer name that is unique within this project. | Correct the request as described, refresh the owned service, and retry. |
APPROVALS_UNAVAILABLE | The owner-approval lane is unavailable; this change cannot proceed. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
OWNER_APPROVAL_REQUIRED | The owner must approve this exact change before it can proceed. | Ask the owner to resolve or approve the stated requirement before retrying. |
COST_GATE_UNAVAILABLE | The authoritative cost gate is unavailable. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
TRANSITION_OUTAGE_CONFIRMATION_REQUIRED | Confirm the planned interruption before changing the service address. | Ask the owner to resolve or approve the stated requirement before retrying. |
PUBLIC_INGRESS_REMAINS | Public ingress remains; the private transition cannot proceed. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
SERVICE_SETTINGS_CHANGED | The service settings changed; refresh and retry. | Correct the request as described, refresh the owned service, and retry. |
REFERENCE_APPLY_FAILED | The consumer variable apply failed and remains unacknowledged. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
REFERENCE_PROPAGATION_PENDING | Consumer acknowledgements are pending; the old route is retained. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
INVALID_OUTBOX_INTENT | The durable service work could not be verified. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
SERVICE_NOT_FOUND | Choose an existing service in the owned project. | Correct the request as described, refresh the owned service, and retry. |
SERVICE_BUSY | The service has another operation in progress. | Correct the request as described, refresh the owned service, and retry. |
SERVICE_OPERATION_STALE | The service operation generation is stale. | Correct the request as described, refresh the owned service, and retry. |
SERVICE_HAS_RESOURCES | A service with machines, disks, snapshots or pending resources cannot be removed here. | Correct the request as described, refresh the owned service, and retry. |
BUILD_IN_PROGRESS | Wait for the current service build to finish. | Correct the request as described, refresh the owned service, and retry. |
NETWORK_UNVERIFIED | The exact project network could not be verified. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
NETWORK_MISMATCH | The service is outside its owned project network. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
CONSENT_REQUIRED | Accept the current Terms before authorizing this cost. | Ask the owner to resolve or approve the stated requirement before retrying. |
TENANT_CAPPED | This account is capped and cannot start additional paid work. | Ask the owner to resolve or approve the stated requirement before retrying. |
TENANT_SUSPENDED | This account is suspended; contact support. | Ask the owner to resolve or approve the stated requirement before retrying. |
NO_CARD | The older billing plan requires a valid card before this cost can proceed. | Ask the owner to resolve or approve the stated requirement before retrying. |
PAYMENT_EXHAUSTED | Resolve the older billing plan payment before this cost can proceed. | Ask the owner to resolve or approve the stated requirement before retrying. |
WALLET_CUTOVER_REQUIRED | Complete the wallet billing transition before authorizing this cost. | Ask the owner to resolve or approve the stated requirement before retrying. |
WALLET_COMPUTE_DISABLED | Wallet-funded compute is unavailable; this account cannot proceed without billing protection. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
INSUFFICIENT_AVAILABLE_BALANCE | Add available prepaid balance before authorizing this cost. | Ask the owner to resolve or approve the stated requirement before retrying. |
INSUFFICIENT_WALLET_BALANCE | Add available prepaid balance before authorizing this cost. | Ask the owner to resolve or approve the stated requirement before retrying. |
BILLING_DATA_UNAVAILABLE | The authoritative billing data is unavailable. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
TRIAL_SERVER_LIMIT_REACHED | This account has reached its welcome-credit server limit. | Ask the owner to resolve or approve the stated requirement before retrying. |
TRIAL_ROUTE_UNSUPPORTED | This operation is unavailable on welcome credit. | Ask the owner to resolve or approve the stated requirement before retrying. |
SIZE_NOT_OFFERED | Choose an offered server size. | Correct the request as described, refresh the owned service, and retry. |
SIZE_NEEDS_PAID_TOPUP | A paid top-up is required for this server size. | Ask the owner to resolve or approve the stated requirement before retrying. |
PAYMENT_REQUIRES_ACTION | Complete the payment verification before proceeding. | Ask the owner to resolve or approve the stated requirement before retrying. |
PAYMENT_PENDING | Wait for the pending payment to complete. | Ask the owner to resolve or approve the stated requirement before retrying. |
PAYMENT_METHOD_REQUIRED | Add an eligible payment method before proceeding. | Ask the owner to resolve or approve the stated requirement before retrying. |
PAYMENT_METHOD_DECLINED | Use another payment method before proceeding. | Ask the owner to resolve or approve the stated requirement before retrying. |
METERING_STALE | Current metering evidence is unavailable. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
POLICY_NOT_CONFIGURED | The required billing policy is unavailable. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
TOPUP_LIMIT_REACHED | The top-up limit has been reached. | Ask the owner to resolve or approve the stated requirement before retrying. |
ALWAYS_ON_CONFIRMATION_REQUIRED | Confirm the always-on price before starting this service. | Ask the owner to resolve or approve the stated requirement before retrying. |
WORKER_NOT_READY | The worker did not stay started through its readiness check. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
PRIVATE_ADDRESS_UNVERIFIED | The private address mode has not been verified in the exact project network. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
PRIVATE_ADDRESS_AMBIGUOUS | Multiple private addresses were found; the address must be reconciled before proceeding. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
PRIVATE_ADDRESS_MODE_CONFLICT | The private address mode conflicts with the recorded routing proof. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
PRIVATE_INGRESS_PRESENT | Public ingress remains on the private service. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
SERVICE_SERVER_COUNT_UNSUPPORTED | Multiple server machines were found for this service; reconcile the one-server invariant before changing its cost. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
MACHINE_DELETION_UNVERIFIED | The old machine deletion could not be verified; no replacement was created. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
FEATURE_DISABLED | This feature is unavailable for this account. Do not allocate resources or bypass its availability check. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
PAID_TOPUP_REQUIRED | This operation requires an eligible paid account. | Ask the owner to resolve or approve the stated requirement before retrying. |
FEATURE_CONSENT_REQUIRED | The owner must accept the specific current feature terms before authorizing this operation. | Ask the owner to resolve or approve the stated requirement before retrying. |
LEGAL_DECISION_UNAVAILABLE | The required approved feature policy is unavailable. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
ONDEMAND_STATE_UNKNOWN | The owned service state could not be verified. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
ONDEMAND_UNAVAILABLE | The requested optional service operation is unavailable. No provider error body is exposed. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
NETWORK_POLICY_RANGES_UNVERIFIED | The current network policy ranges could not be verified. No policy change was applied. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
NETWORK_POLICY_UNVERIFIED | The applied network policy did not pass its exact owned-network checks. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
OPAQUE_NAMES_UNAVAILABLE | The approved private app naming capability is unavailable. No app was created. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
ROUTER_ADDRESS_AMBIGUOUS | Multiple router-network addresses were found. Reconcile the owned route before publishing it. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
ROUTER_ROUTE_INVALID | The shared route does not match the owned service routing contract. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
ROUTER_STATE_UNKNOWN | The shared router state could not be verified. Keep the stored route and reconcile the original operation. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
ROUTER_TRIGGER_UNMET | Shared routing has not met its approved activation conditions. No router was allocated. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
WILDCARD_CERTIFICATE_UNVERIFIED | The shared wildcard certificate could not be verified. No public route was published. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
TEMPLATE_MANIFEST_CHANGED | The immutable template recipe changed. Refresh and review the current template before trying again. | Correct the request as described, refresh the owned service, and retry. |
TEMPLATE_REVIEW_STALE | The template or its affected consumers changed after review. Refresh the plan and confirm the current operation. | Correct the request as described, refresh the owned service, and retry. |
TEMPLATE_TLS_TRUST_UNAVAILABLE | The exact current template TLS trust certificate could not be verified. Do not disable certificate verification. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
PUBLIC_TCP_TEMPLATE_NATIVE_UNSUPPORTED | This private datastore recipe cannot safely expose its native port. Keep it private or use a supported external connection path. | Correct the request as described, refresh the owned service, and retry. |
INVALID_TEMPLATE_REQUEST | Use a stable unique request identity for this exact template operation. | Correct the request as described, refresh the owned service, and retry. |
TEMPLATE_MAJOR_UPGRADE_REQUIRED | A major database upgrade requires a reviewed new template, safety snapshot, dump and restore, data verification and relinking. Keep the original until verification succeeds. | Correct the request as described, refresh the owned service, and retry. |
TEMPLATE_UPGRADE_UNAVAILABLE | A reviewed immutable same-major template update is unavailable. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
TEMPLATE_OUTAGE_CONFIRMATION_REQUIRED | Review the affected consumers and confirm the temporary connection outage before continuing. | Ask the owner to resolve or approve the stated requirement before retrying. |
TEMPLATE_ROTATION_UNCERTAIN | The database password change is uncertain. Reconcile the saved request instead of sending another password change. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
TEMPLATE_LIFECYCLE_STATE_UNKNOWN | The saved template operation state could not be verified. Keep the original request identity for reconciliation. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
TEMPLATE_SAFETY_SNAPSHOT_UNAVAILABLE | A verified safety snapshot is required before changing the template image. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
TEMPLATE_SAFETY_SNAPSHOT_PENDING | The safety snapshot is not yet verified. Check the saved request before another snapshot or template update. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
TEMPLATE_UPGRADE_UNCERTAIN | The template image update is uncertain. Verify the saved operation before another provider update. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
TEMPLATE_OPERATION_REQUIRED | Use the reviewed template lifecycle controls for this database service. Generic settings or deployments cannot replace its managed recipe. | Correct the request as described, refresh the owned service, and retry. |
TEMPLATE_SECRET_VERSION_UNVERIFIED | The current provider secret version could not be verified. No template update was sent. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
SECRET_VERSION_UNVERIFIED | The current secret update could not be verified. No server update was sent. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
SECRET_WRITE_PENDING | A previous secret update is still unresolved. Keep the original request and wait for reconciliation before updating the server. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
SECRET_WRITE_SCOPE_UNVERIFIED | The owned service and its secret update could not be verified. No secret update was sent. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
TLS_HOST_INVALID | Choose a valid TLS hostname for the owned service. | Correct the request as described, refresh the owned service, and retry. |
PROVIDER_TIMEOUT | The provider timed out. The operation needs reconciliation before another allocation. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
PROVIDER_RESPONSE_UNVERIFIED | The provider result could not be verified. Do not repeat paid allocation. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
PORT_NOT_ALLOWED | Choose an allowed public TCP port. | Correct the request as described, refresh the owned service, and retry. |
PUBLIC_PRIVATE_PORT_ISOLATION_UNVERIFIED | Public and private port isolation is unverified. Existing private consumers and ports are preserved. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
ADDON_PRICING_UNAVAILABLE | The approved addon price and bounded absorption policy are unavailable. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
ADDON_RUNTIME_UNAVAILABLE | The real addon runtime or exact machine inventory is unavailable. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
ADDON_ALLOCATION_UNKNOWN | Addon allocation is uncertain. Reconcile the original operation instead of allocating another IP. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
ADDON_INVENTORY_UNKNOWN | The complete owned addon inventory could not be verified. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
ADDON_RELEASE_UNVERIFIED | Provider addon release could not be confirmed. Billing reconciliation remains pending. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
ADDON_METER_UNAVAILABLE | Authoritative addon metering is unavailable. No provider mutation is authorized. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
APPROVAL_STALE | The reviewed state changed. Refresh the exact operation and request a new owner review. | Correct the request as described, refresh the owned service, and retry. |
APPROVAL_INVALID | Use an approval for this exact owned action, parameters and current state. | Correct the request as described, refresh the owned service, and retry. |
PUBLIC_ENDPOINT_UNVERIFIED | The applied public endpoint could not be verified. Do not claim that the service is reachable. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
TEMPLATE_RUNTIME_UNAVAILABLE | The real private template runtime is unavailable. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
TEMPLATE_MANIFEST_UNAVAILABLE | The immutable template recipe and approved policy are unavailable. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
INVALID_LINK_INPUT | Choose valid, owned service link targets. | Correct the request as described, refresh the owned service, and retry. |
TEMPLATE_NOT_READY | The datastore template did not pass its readiness checks. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
CREDENTIAL_STORE_UNAVAILABLE | The write-only template credential store is unavailable. No credential is returned. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
TLS_GENERATION_UNAVAILABLE | Template TLS credentials could not be generated safely. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
SNAPSHOTS_UNAVAILABLE | Snapshot operations are unavailable for this account. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
TEMPLATE_VOLUME_UNVERIFIED | The exact owned template disk could not be verified. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
EGRESS_PAIR_MACHINE_LIMIT | Reconcile the supported machine count before allocating a static egress pair. | Correct the request as described, refresh the owned service, and retry. |
PUBLIC_TEMPLATE_TLS_UNVERIFIED | The immutable template TLS backend configuration could not be verified. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
STORAGE_UNAVAILABLE | The real owned storage operation is unavailable. No provider error body is exposed. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
STORAGE_NEEDS_ATTENTION | The storage operation requires reconciliation. Do not repeat replacement or allocation. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
VOLUME_EXTEND_UNAVAILABLE | Disk extension is unavailable for this account. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
VOLUME_NOT_FOUND | Choose an existing disk of the exact owned service. | Correct the request as described, refresh the owned service, and retry. |
VOLUME_SIZE_INVALID | Choose a valid disk size within the supported limits. | Correct the request as described, refresh the owned service, and retry. |
VOLUME_SHRINK_UNSUPPORTED | Disks can only grow. A smaller replacement requires an explicit data migration. | Correct the request as described, refresh the owned service, and retry. |
VOLUME_CAPACITY_REQUIRED | The requested physical disk capacity exceeds the approved limit. Keep the existing disk and review capacity. | Ask the owner to resolve or approve the stated requirement before retrying. |
SNAPSHOT_SETTINGS_INVALID | Choose at least one valid future snapshot setting. | Correct the request as described, refresh the owned service, and retry. |
SNAPSHOT_SETTINGS_UNAVAILABLE | The authoritative snapshot settings or template policy could not be verified. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
TEMPLATE_SNAPSHOT_REQUIRED | This template requires daily snapshots and its minimum retention. Keep its required protection. | Correct the request as described, refresh the owned service, and retry. |
TEMPLATE_SNAPSHOT_POLICY_REQUIRED | Disabling optional template snapshots requires its exact approved owner review and policy. | Ask the owner to resolve or approve the stated requirement before retrying. |
SNAPSHOT_INVENTORY_UNKNOWN | The complete snapshot inventory could not be verified. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
SNAPSHOT_NOT_FOUND | Choose an existing snapshot of the exact owned disk. | Correct the request as described, refresh the owned service, and retry. |
RESTORE_CAPACITY_REQUIRED | Old, new and pending disks all count. Review the required physical capacity before restoring. | Ask the owner to resolve or approve the stated requirement before retrying. |
STORAGE_RESTORE_UNAVAILABLE | The real encrypted restore runtime is unavailable. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
INSPECT_UNAVAILABLE | Positive isolated inspection containment is unavailable. No inspection server is created. | Do not bypass the check or allocate resources; contact support if verification remains unavailable. |
APPROVAL_REQUIRED | The owner must review and approve this exact action before it executes. | Ask the owner to resolve or approve the stated requirement before retrying. |
Persistent data
A server's own disk is ephemeral: everything the app writes there — SQLite files, uploads, JSON/file stores, file sessions — is wiped on every redeploy, restart, and sleep/wake — tell the user so when a deploy goes live. An app that keeps any state needs one of:
- Managed Postgres —
lavela.provision_database(a direct connection string, not the pooled one — the same string on a retry; lavela takes no backups — the database provider keeps only a short point-in-time history, so take your own dumps for long-term backups).wireToServicestoresDATABASE_URLon that server as a secret — it must name a server of the project that runs or is being built (a typo is refused withSERVICE_NOT_FOUNDand creates nothing); the connection string is returned only when it was not stored on a server. Its region is fixed when it is created and the result says which (an EU-only account's databases are created in Frankfurt). It is free within these limits per database: 0.5 GB storage, 100 compute-hours and 5 GB of data transfer a month (past the compute or transfer allowance it pauses until next month; past the storage cap, writes fail). An account has a limit on lavela databases — a hosted app's own database and databases left by deleted projects count too (MANAGED_DB_LIMIT_REACHEDnames those, with the iddestroy_resourcetakes). Calling it again with the same name (after a timeout, say) returns that database (adopted: true) instead of creating a second one; a name another resource or server of the project already uses is refused (SERVICE_NAME_TAKEN). - Redis — lavela has no managed Redis: it creates no Redis databases. Use a Redis URL from the user's own provider (for example a free Upstash database under their own account) and store it with
set_compute_secretasREDIS_URL. Or, on a funded account (not the welcome credit), run valkey or redis as the user's own server:provision_computewith avalkey/valkeyorredisimage runs it private and always on — no internet address, its always-on price confirmed first, its disk ephemeral like any server's (mount a volume if the data must survive restarts) — then setREDIS_URLon the app to its private address (compute_wiregives it). - A volume —
provision_volume, thenmounts: [{volume: "vol_…", path: "/data"}]onprovision_compute/provision_compute_from_repo(use the folder the app writes to). One volume belongs to one server and region and is not replicated, and lavela can't change its size later — pick enough. A redeploy that leavesmountsout keeps the attached volume (compute_rebuildkeeps it too); a different set recreates the server. A volume is billed while it exists, also while its server is stopped or after the server is removed. Fly keeps a daily snapshot of each disk for up to 5 days by default (the create result states the number). It is not a backup service, and lavela cannot restore it for you — take your own dumps of data you can't lose. - Object storage —
provision_storageis disabled on most deployments (shared-key risk); use a volume or the database.
These need a funded account (not the welcome credit). Managed Postgres has no usage charge today — a per-account limit bounds it. On the welcome credit, or to keep the data with a provider of your own: Bring your own database.
When creating a disk or a database is refused (nothing was created or charged):
| Error | Meaning | What to do |
|---|---|---|
VOLUME_NEEDS_BALANCE | A new disk may need about 30 days of its price available on the balance (the error states the amount), and nothing owed — checked only while lavela's disk balance check is on | Tell the user the amount, have them add funds (topup_link), then call again — or choose a smaller disk |
VOLUME_LIMIT_REACHED | The account's disk limit: the error says whether it is the number of disks or the total GB (every disk counts, attached or not), or that new disks are closed for the account | Free a disk the user no longer needs (list_resources → destroy_resource, with their agreement: its data is deleted; remove the server using it first), choose a smaller size, or contact support to raise the limit — when the error says new disks are closed, only support can open them |
VOLUME_GLOBAL_LIMIT, VOLUME_LIMITS_UNAVAILABLE | lavela can't create new disks right now: its own disk capacity is full (VOLUME_GLOBAL_LIMIT) or its disk limits couldn't be read (VOLUME_LIMITS_UNAVAILABLE) — lavela's side, not the account's | Don't retry in a loop: tell the user and try again later; contact support if it persists |
VOLUME_IN_USE | destroy_resource of a disk a server still uses — nothing was deleted | Remove the server that uses it first (destroy_compute, with the user's confirmation), then delete the disk; a redeploy without mounts keeps it attached |
MANAGED_DB_LIMIT_REACHED | The account's limit on lavela databases (a hosted app's own database and ones left by deleted projects count; the error names those, with their ids). "… closed … right now" means deleting one frees nothing | Free one the user no longer needs (destroy_resource, with their agreement: its data is lost), or store a Postgres URL from the user's own provider as DATABASE_URL with set_compute_secret (Bring your own database) |
MANAGED_POOL_FULL | lavela's own database capacity is full right now — not the account's limit | A Postgres URL from the user's own provider (Bring your own database), or try later |
MANAGED_CACHE_UNAVAILABLE | lavela creates no Redis databases of its own | A Redis URL from the user's own provider, stored as REDIS_URL with set_compute_secret — or run redis or valkey as the user's own server (see Redis above) |
SERVICE_NOT_FOUND | wireToService names no server of this project (a typo) | Use a name project_overview shows, or deploy that server first |
Reaching another service. All of an account's servers, in every project, share one private network. One server reaches another at <app>.internal on the port it listens on (compute_wire gives the address). That address answers only while the server runs: nothing wakes a server that sleeps when idle, or a stopped one, through it — so a database or an internal API that other services call must be always on (compute_always_on; it costs more — confirm with the user). The server must also listen on IPv6 (::): an app bound to 0.0.0.0 only can't be reached there. A one-off job or a build of that service shows up at the same address while it runs, so a connection can occasionally be refused — retry it.
Migrations: run_compute_job with the app's image — a job that outlasts ~180 seconds keeps running in the background on its own server, which stays billed (asleep once the job ends) until destroy_compute removes it; don't run it twice (pass the same idempotencyKey on a retry).
Bring your own database
Create a free Supabase or Neon database under your own account and set DATABASE_URL with set_compute_secret before or after your first deploy.
- Supabase: use the Session pooler connection (
aws-…pooler.supabase.com:5432): it suits a long-running server and works over IPv4, while Supabase's direct host is IPv6-only. Use the transaction pooler (6543) only with?pgbouncer=true(Prisma) orprepare: false(postgres.js, Drizzle): it does not support prepared statements. - Neon: use the pooled connection string.
- Do not turn on an IP allowlist: lavela's outbound IPs are not static.
Set the whole connection string as the value: ${{ … }} references between services aren't available yet (REFERENCES_UNAVAILABLE).
Domains
lavela.provision_domain connects a domain the user already owns — lavela doesn't sell domains — and returns the DNS records to add at their registrar. target: "hosting" (default) attaches it to the web-hosting site, which verifies and gets HTTPS by itself once the records propagate. target: "compute" attaches it to a server (service, default web), which doesn't verify by itself — poll get_status. The app must be deployed first.
lavela.diagnose_domain checks each expected record against DNS and the HTTPS certificate, and returns a fix list in plain words:
| Record status | Meaning |
|---|---|
verified | Found, correct |
propagation_pending | Not visible yet — DNS changes usually take 10–30 minutes, sometimes hours; check again later |
wrong_target | A record exists but points somewhere else — change it to the expected value |
txt_conflict | Several TXT values at that name and none is the expected one — add the expected value |
check_failed | The lookup itself failed — try again |
The certificate is issued, not_yet (usually follows the records within minutes) or check_failed (couldn't check — try again). A common mistake: registrars that append the domain to the name themselves turn www.example.com into www.example.com.example.com — enter only www.
lavela.provision_email sets up DKIM / SPF / DMARC on a domain the user owns (needs a funded account). Ownership is proven automatically when the domain is this project's live connected domain (provision_domain, DNS pointing at it) — its records are then published automatically. Otherwise the result carries an ownershipVerification TXT record: the user publishes it, then call provision_email again (nothing is set up for the domain until then), and publish the records it returns. lavela-owned domains (lavela.dev and subdomains) are refused. Poll get_status for verification.
send_email (advanced) sends from an address on that verified domain. Pass the same idempotencyKey on a retry so nothing is sent twice. EMAIL_DAILY_CAP: the daily limit is reached (lower for accounts under 14 days old; an account with only the welcome credit can't send email until a paid top-up) — wait for the reset. EMAIL_RATE_LIMIT_UNAVAILABLE: retry in a few minutes.
Email on the welcome credit. provision_email and send_email are refused there (TRIAL_ROUTE_UNSUPPORTED, action email_send) until a paid top-up. Meanwhile the app can send its own mail with the user's own Resend account: its API key set with set_compute_secret (for example RESEND_API_KEY).
Payments
lavela.connect_stripe lets the user's app take payments — confirm with the user first; it starts Stripe's identity checks (KYC, commonly 1–5 days). mode: "oauth" connects an existing Stripe account (it can be unavailable — then use mode: "new"); mode: "new" creates one with embedded onboarding. Poll get_status for the onboarding link and KYC state. If the user already has Stripe connected on another of their projects, mode: "new" answers status: "choice_required" with options and starts nothing — ask whether to reuse one (reuseAccountId) or create a separate account (forceNew: true). A revoked Stripe module means payments are broken until reconnected.
lavela.create_checkout creates a Checkout link for the user's customers. The charge runs on the user's connected Stripe account and settles to them in full — lavela takes no per-charge fee and is not the merchant. It needs KYC complete. amountCents is the smallest currency unit (1999 = $19.99) — except zero-decimal currencies (JPY, KRW, VND, CLP and the others Stripe lists), where it is the raw amount (5000 = ¥5,000). Getting this wrong overcharges real customers about 100x and Stripe can't catch it: confirm the price and currency with the user first. mode: "subscription" with an interval makes a subscription. Pass an idempotencyKey when retrying.
Money
- What a server costs. A server that sleeps when idle is billed by use, by the second (
pricingModel: "asleep_awake"— the current Terms' pricing): its size's asleep price while it sleeps and its always-on price while it is awake — so never more than kept always on. Quote it as a range,asleepMonthlyUsdtocapMonthlyUsd(the always-on price, the most it can cost a month), never as one monthly price;monthToDateUsdis what it has cost so far this month andawakeSharethe share of its billed time this month billed at the awake rate — awake, or kept always on.awakeShareis null while the pace is not known — until the server has a day of billed time this month, and for the rest of a month in which its service ran before its current prices applied (a server it replaced included) — andestMonthlyUsdis then its always-on price, not a pace (when the month can't be read,monthToDateUsdandawakeShareare null andestMonthlyUsdis the always-on price).compute_statusgives the account'smonthEstimateUsdandmonthMaxUsd: monthly rates of the servers billing now, not this month's total; itspaceUnknownis true whenmonthEstimateUsdcounts a sleeping server at its always-on price for want of a pace. It wakes on any request and sleeps again a few minutes after the last one, so something that calls it every few minutes (an uptime monitor, a polling client) keeps it awake all month at the always-on price — then suggest always on: the same price, no start-up delay. An always-on server pays its always-on price every second. An account that has not accepted the current Terms (pricingModel: "flat") is billed under the Terms it accepted: a flat monthly price per server,sleepingMonthlyUsd, which is null underasleep_awake. One-off jobs and schedules are billed at the smallest size's always-on price while they run. A one-off job still running after ~180 seconds goes on in the background on its own server, which stays billed (asleep once the job ends) untildestroy_computeremoves it. - The wallet. Servers, one-off jobs, schedules and volumes are paid from the user's prepaid lavela wallet. Prices: /pricing.
lavela.account_statushas the balance and what can start (canUse) — decide fromcanUseandwallet, notbillingMode(an account-level label that can read "legacy" on a wallet account). Top-ups happen in the console (lavela.topup_link) — an agent never takes card details or moves money; saving a card doesn't charge it by itself. Cards issued in the US or Canada pay lavela directly; cards from other countries (Korean cards, Kakao Pay, Naver Pay and others) pay on the same page through Stripe's Link checkout, which sells the credit and adds local tax on top. Automatic top-up needs a US or Canadian card. - The free welcome credit. An eligible new account gets it once (one per person), automatically, when the user accepts the Terms and has confirmed their email — no card needed. Mention it only when
account_statusshowstrial.claimable(an account that already has it, or cannot get it, does not). It covers one 512 MB server built from a GitHub repo (the default size — every app deploys that way on the credit, a web frontend too) and its secrets; bigger sizes need a paid top-up (Deploy from repo). While the account has only the welcome credit its builds are limited too — 20 a month for the whole account, each up to 10 minutes (BUILD_MONTHLY_LIMIT_WELCOMEonce the month's are used; a longer build failsBUILD_TIMEOUT). Web hosting, databases, schedules, one-off jobs, volumes, uploads, email, ads and previews need a paid top-up — any top-up lifts every trial limit, the build limits included. - When a lane is closed on the welcome credit (
TRIAL_ROUTE_UNSUPPORTED; itsparams.actionnames the lane, and the error'suser_actionsays the same), a paid top-up opens it. Meanwhile: web hosting → deploy the repo as a server withprovision_compute_from_repo, which takes every app on the credit; a preview environment → deploy the branch to try as the trial's server (provision_compute_from_repowithgitRef, orcompute_rebuildwithgitRefonce a server runs — that replaces the running server, so get the user's OK); error tracking → a free Sentry project under the user's own account, its DSN set asSENTRY_DSNwithset_compute_secret(the app needs Sentry's SDK, which the user's own coding agent adds — lavela never edits their code;get_logsmeanwhile); analytics → the user's own Plausible or Google Analytics snippet in the app; a database → a free Supabase or Neon database asDATABASE_URL(Bring your own database); email → the app sends with the user's own Resend account, its key set withset_compute_secret(for exampleRESEND_API_KEY). Scheduled jobs have no alternative on the credit yet. - When the free credit is used up, the server keeps running until
trial.graceEndsAt(alsowallet.graceEndsAt) and then stops unless the user adds funds. That grace is free — it doesn't become a debt. - When a paid balance runs out, running servers keep running until
wallet.graceEndsAtat the latest; the usage until then becomes a negative balance (wallet.debt) taken from the next top-up. Once that unpaid amount grows beyond its limit (Terms Section 7.1; the amounts are on /pricing), the servers stop right away instead —computeshows the pause (reasonwallet_low_balance) andwallet.graceEndsAtmoves to that moment — so never promise the user the full grace. Either way nothing new — builds, redeploys, jobs, volumes, starts — runs until a top-up brings the balance above zero, and after that time the servers stop. - After a top-up the pause lifts once the payment settles (if the balance already covers it but compute is still paused,
lavela.resume_computelifts it now). Stopped servers stay stopped until started —compute_lifecycleactionstartwith the machine ids fromproject_overview. - Ads. The ad spend itself is on the user's own card at each platform; lavela charges a management commission, to the user's card once a month, on the ad spend in the ad account it set up for them — every campaign in it, including any they run there themselves (see /pricing).
- The user's sales settle 100% to them via Stripe Connect.
Destructive actions
Before any of these, show the user the exact target — the project's name, the server, the resource — and wait for their confirmation.
| Tool | Removes | Keeps |
|---|---|---|
delete_project | The project, its servers, its disks and their snapshots at once (with all data on them), its web-hosting deployment and its email domain — irreversible. Refused while an ad campaign is live (pause it first) | Managed databases, caches and storage, unless the operator enabled data release (the result's dataKept says which) — to delete them, do it BEFORE deleting the project: list_resources → destroy_resource (advanced) |
destroy_compute | One server: it stops being charged. Its own disk is lost | An attached volume (still billed), its daily snapshots and managed databases stay until you delete them or the project |
destroy_resource | A managed database/cache/storage/error-tracking project, or a volume, with ALL its data. A volume (disk) is deleted now; Fly keeps its daily snapshots for their retention (5 days by default), which cannot be restored through lavela, and then deletes them | — A hosted app's live database (usedBy: "hosted_app") is refused unless confirmHostedAppDatabase: true after the user explicitly agreed |
rollback | Nothing — it changes which deployment is live | Everything |
archive_project | Nothing — it only hides the project; servers keep running and being charged | Everything |
preview_destroy, schedule_delete | A preview environment / a cron schedule | The project |
lavela itself requires a few confirmations in an agent's request, and you cannot skip them: delete_project needs the project's exact name in confirmName (a mismatch is refused); ads_launch and ads_set_budget need the daily budget repeated (confirmDailyBudgetCents, confirmNewDailyBudgetCents); destroy_resource on a hosted app's live database needs confirmHostedAppDatabase: true; and only the user can accept the Terms (in the browser), add funds and turn on automatic top-up (in the console). These requirements only make sure the request repeats the target or amount; they show the user nothing. lavela adds no other confirmation prompt to your requests, so the confirmation above is yours to get.
Four advanced tools are also marked destructive in the tool list, so an MCP client asks before each call: exec_compute (a command can change a server's data), run_compute_job (a job changes data and is billed), ads_launch (a live campaign spends real money daily) and send_email (a sent email cannot be recalled). Confirm the exact target with the user — the server and command, the daily budget, the recipients — before calling them.
Advanced tools
Connect with https://console.lavela.dev/api/mcp/all (Connect) for these: lavela.trial_claim_link, lavela.ping, lavela.capabilities, lavela.list_templates, lavela.configure_service, lavela.link_services, lavela.volume_extend, lavela.volume_snapshots, lavela.template_status, lavela.template_rotate, lavela.template_upgrade, lavela.template_lifecycle_status, lavela.compute_exec_history, lavela.volume_restore, lavela.public_port_link, lavela.egress_ip_link, lavela.compute_status, lavela.compute_list, lavela.compute_estimate, lavela.provision_compute, lavela.push_image, lavela.compute_set_size, lavela.compute_wire, lavela.run_compute_job, lavela.schedule_create, lavela.schedule_list, lavela.schedule_update, lavela.schedule_delete, lavela.schedule_run_now, lavela.preview_list, lavela.preview_destroy, lavela.preview_webhook_setup, lavela.exec_compute, lavela.provision_storage, lavela.provision_volume, lavela.destroy_resource, lavela.send_email, lavela.billing_setup_card, lavela.archive_project, lavela.unarchive_project, lavela.compliance_generate, lavela.compliance_list, lavela.security_scan, lavela.security_scan_status, lavela.list_security_scans, lavela.provision_error_tracking, lavela.get_errors, lavela.daily_brief, lavela.list_deployments, lavela.rollback, lavela.ads_platforms, lavela.ads_draft, lavela.ads_launch, lavela.ads_status, lavela.ads_pause, lavela.ads_provision_account, lavela.ads_assets_push, lavela.ads_setup_tracking, lavela.ads_report, lavela.ads_conversions, lavela.ads_set_budget, lavela.ads_commission.
- Images and servers.
provision_computeruns an image already pushed to a registry (lavela doesn't build it);push_imagegives the user a command to push a private one (npx @lavela/cli@latest push <image> --project <id>): their browser authorizes a one-hour push key for that one image repository, revoked when the push ends — no key or password passes through the agent — and the CLI prints theregistry.lavela.dev/…ref to pass toprovision_compute. No agent or user ever gets a hosting-provider credential. If lavela's image registry is switched off,push_imageand repo builds answerREGISTRY_UNAVAILABLE(lavela's side; nothing is started or counted) — retry later. Callingprovision_computeagain for the same service redeploys its server (that call's image, env, port and checks replace the old ones; size, always-on and volumes are kept when left out), and its URL isn't readiness-checked; itschecksdon't hold back traffic.compute_listlists servers with their ids, sizes and prices (for a server that sleeps when idle:asleepMonthlyUsd,capMonthlyUsd,monthToDateUsd,awakeShareandestMonthlyUsd, this month's pace);compute_statusshows limits and usage (on a wallet or trial account itsaccountblock is what counts — tier and spend-cap fields are legacy).exec_computeruns a command of up to 60 seconds inside a running server;run_compute_jobruns one on a temporary small server (a job past ~180 seconds keeps running in the background, and its server stays billed untildestroy_computeremoves it — don't re-run it).compute_estimatequotes a spec. - Schedules.
schedule_createruns a command on a standard 5-field cron in UTC, at most every 5 minutes, 20 per project; a firing still running makes the next one skip. Each firing is billed. Itsenvis stored for the schedule's lifetime — never put secrets there.schedule_listoutcomes:skipped_balance(the wallet ran out; resumes after a top-up),skipped_refused(nothing ran or was charged —lastErrorisCODE: what to do: accept the Terms, lavela paused new paid services, contact support, or the org guard'sORG_CAPACITY_REACHED(lavela is at capacity and has been alerted),TRIAL_CAPACITY_FULL(free trial servers are full: add funds) orACCOUNT_MACHINE_LIMIT(the account's machine limit); the schedule stays on and each firing is checked again at its time),skipped_dispatch_error(a passing hiccup, not a job failure),skipped_paused,skipped_capped. - Preview environments. Created automatically for pull requests once the user adds the GitHub webhook from
preview_webhook_setup(its secret is shown once). Web-hosting projects only, and previews get no client env — an app whose database lives outside lavela fails them withDB_ENV_MISSING. They are removed when the PR closes (preview_destroyremoves one early). - Security scans.
security_scanchecks npm lockfiles for vulnerable dependencies and the repo for committed secrets; pollsecurity_scan_status. Always relaysecrets[]; only statusokwith no secrets is a clean result. A private repo is scanned only when the server's own GitHub access can read it (clone_failedotherwise). - Legal documents.
compliance_generatedrafts a privacy policy, terms and a cookie banner — not legal advice;model: "stub"means a basic template. - Ads. Drafts for 10 platforms; lavela launches through the API only on Google (Search network) and Meta, where their credentials are configured (Meta also needs App Review) — everything else is a guided handoff the user finishes in the platform. Launching and budget changes echo the agreed daily budget (
confirmDailyBudgetCents); a live campaign spends daily on the user's card untilads_pause.ads_commissionis metered from Google API spend only and can under-report.
Errors and support
A tool error is { "error": { "code", "reason", "actor", "message", "user_action"?, "say"?, "docs_url", "retry_after_ms"? } }. Most carry user_action: do what it says first — the fix is usually self-serve (a link the user opens, a retry, another tool) — and don't retry in a loop when the user must act first. When it is missing, use the table below; a malformed call can also fail with a protocol error outside this shape.
reasonis the specific cause, such asTRIAL_SERVER_LIMIT_REACHEDorCONSENT_REQUIRED— the same ascodewhen there is nothing more specific.say, where there is one, is a plain sentence for the user, kept apart fromuser_action(what you do) — relay it in your own words.docs_urlis the section of this page (or the console page) that explains the code; the support page only where contacting support is the fix.actorsays who has to act:agent(change the call, use another tool, or wait and retry),user(accept the Terms, add funds, confirm, reconnect or contact support) orlavela(a problem or limit on lavela's side — retry later; if it persists, contact support).
| Code | Meaning |
|---|---|
FORBIDDEN | A policy, limit, trial or balance refusal — don't retry as-is; user_action names the fix |
UNAUTHORIZED | The connection isn't signed in (or the sign-in expired) — reconnect lavela in the client |
NOT_FOUND | Wrong id (also a bare CROSS_TENANT refusal: a mistyped id, or one that belongs to another account) — copy ids from list_projects / project_overview instead of retyping them |
RATE_LIMITED | Wait retry_after_ms, then retry the same call |
CONFLICT | Something is already in progress — poll it and let it finish |
BAD_REQUEST, INVALID_INPUT | Fix the input as the message says — also a request lavela refuses until the call changes (SERVICE_NAME_INVALID, DATABASE_NEEDS_ALWAYS_ON: reason and user_action say how); never retried as is. An INVALID_INPUT whose message names nothing wrong with what you sent is a fault: read back first (launch_saas: list_projects) so a repeat doesn't create a second project |
INTERNAL | A problem on lavela's side — nothing in the call was wrong. A read-only or repeat-safe (idempotent) tool: retry the same call once. A tool that creates or changes something (launch_saas, a deploy, an ad launch, a delete, …) may have gone through before the error came back, so don't repeat it blindly: its user_action names the tool that shows the result (list_projects, project_overview, get_status, list_resources, ads_status, …) — call it again once only if the effect is missing. If it fails again, stop and tell the user (say) — support is the way on |
CONSENT_REQUIRED: the user accepts the current Terms at /consent — once, and again when a new version requires it — then retry. A missing permission (scope): reconnect to refresh it — or, for a static token, the user creates one that has it (Manage connections).
Still stuck? Email support@lavela.dev or open /support — a person reads it, not a bot.
Questions? Email support@lavela.dev · Terms · Privacy · Credits & Refunds