# Launchbase brief for coding agents

You are launching a product for a human (the owner) on Launchbase. Launchbase gives one app everything it needs:
hosting, a Postgres database with user accounts, transactional email, Stripe payments and a domain, all created and
operated through the tools below. You drive it over remote MCP (https://launchbase.build/mcp) or the JSON API (https://launchbase.build/api/v1);
every tool has a JSON twin. The owner approves by email and completes anything involving money. Read this whole
brief once; after that you should not need to discover anything.

Machine documents: https://launchbase.build/api/v1/discovery, https://launchbase.build/api/v1/guide (deploy contract), https://launchbase.build/api/v1/catalog (components, regions, plans, features).

## Ground rules

- **One app at a time.** You work in exactly one app. An app you create becomes current at once; if the
  owner has only one app it becomes current at once; otherwise call switch_app {appId}. The switch
  happens immediately without owner approval. Existing app-bound credentials remain bound.
  list_apps shows every app (name, description, status) and which one is current; whoami shows activeApp.
  Tools on another app answer switch_app_required.
- **App and service removal is protected.** delete_app (and turning off a component that holds data via update_app) returns a URL;
  the owner confirms there by typing the app name. Nothing is removed until they do.
- Stop at every response whose action_required has actor "owner": send the owner the URL or instruction and wait
  (payments, purchases, removing apps or services). Database migrations remain permitted, including destructive SQL; service protection does not prevent deleting tables or data.
- Never invent tokens, card numbers, registrant details or addresses. Ask the owner.
- Never put a Launchbase credential, a provider key or an .env file into the source you deploy.
- Read the error and its next step; do not retry blindly. requestId and idempotencyKey make retries safe.
- Poll at the cadence the response suggests (connect: 5 s; releases: 5–10 s; provisioning: 10 s). Never claim something is ready that you have not read as ready.
- Everything you do is audited under your credential. The owner can revoke it at any time.

## 1. Get access

**The owner already has an account (default).**

    POST https://launchbase.build/api/v1/agent/connect
    {"agentName": "<your name, e.g. Claude Code>", "description": "<what you will do>"}

→ 201 { connectId, userCode, verificationUrl, pollToken, intervalSeconds }. Show the owner the verificationUrl (or the
code, to type at https://launchbase.build/connect). They sign in and approve; they may narrow capabilities or choose your starting app. Poll:

    GET https://launchbase.build/api/v1/agent/connect/<connectId>
    Authorization: Bearer <pollToken>

"pending" → keep polling; "approved" → the response carries token (lb_agent_…), scopes, appId and mcpConfig,
delivered once; "denied"/"expired" → tell the owner and stop.

**The owner has no account yet.**

    POST https://launchbase.build/api/v1/agent/signup
    {"ownerEmail": "<owner email>", "description": "<what the product does, who uses it>"}

→ 202 { draftId, sessionToken (lb_draft_…), proposal, notification }. Launchbase emails the owner from
noreply@launchbase.build: they confirm their email, choose a password and approve the proposed app. Tell them to
look for that email; if it did not arrive give them https://launchbase.build/claim/<draftId>. Meanwhile you may refine the proposal
with draft_status / draft_update (bearer = the draft token). When the owner has approved, call draft_redeem once: it
returns your credential bound to that app.

**Use the credential.** `Authorization: Bearer lb_agent_…` on https://launchbase.build/api/v1 and on https://launchbase.build/mcp.
MCP client configuration:

    {"mcpServers": {"launchbase": {"url": "https://launchbase.build/mcp", "headers": {"Authorization": "Bearer lb_agent_…"}}}}

New connections request all documented capabilities by default, subject to owner consent, app protections and budget policies. Existing credentials are never expanded automatically.
Billing scopes let you request checkout, not approve payment. App and service deletion requires the owner; service-removal policy can block the request.

## 2. Choose the stack (before creating the app)

Pick components from what the app does. Every component is managed | byok | disabled.

| The app… | hosting | database | auth | email | payments | domain |
|---|---|---|---|---|---|---|
| is a static site or landing page | managed | disabled | disabled | disabled | disabled | later |
| is an API or app that stores data, no user login | managed | managed | disabled | as needed | as needed | later |
| has user accounts | managed | managed | managed | managed (confirmations, resets) | as needed | later |
| charges its users (subscriptions, one-off) | managed | managed | managed | managed | managed | recommended |

- auth requires database (same Supabase app). email is needed for anything that sends mail from the app.
- payments: Stripe Connect gives the owner their own Stripe account with dashboard (the owner completes Stripe's onboarding).
- domain: attach one the owner controls or buy one; paid plans only. The generated URL works from day one.
- start: "existing" for the owner's own code (yours), "starter" to deploy a Next.js SaaS starter first so the owner sees a running app.
- regions: eu-west (Europe (Ireland / Netherlands)), us-east (US East (Virginia)), us-west (US West (California / Oregon)), ap-southeast (Asia Pacific (Singapore)). Pick the one closest to the owner's users.

Steps: recommend_app {description} gives a proposal with reasons; review_app_config {config} validates
dependencies and shows what will be created and charged; create_app {name, description, config} creates the draft.
Config shape: {"start": "existing", "region": "eu-west", "components": {"hosting": "managed", "database": "managed", "auth": "managed", "email": "managed", "payments": "disabled", "domain": "disabled"}}.
Changing components later: update_app (a live app asks for confirmation before disabling anything).

## 3. Let the owner pay, then provision

- **Trial slots**: One concurrent trial without a verified card, three with one. A testing_limit response includes an owner card link or free_trial_slot action. Ask which app to pause with pause_trial, wait for its job to succeed, then retry. resume_trial uses a slot and preserves trialEndsAt; expired trials require owner-approved payment. Paused snapshots are retained for 30 days after the original deadline.
- **Testing app (free)**: start_trial {appId} (app:write, costs nothing, no owner step) deploys the app for free: public URL <slug>.apps.launchbase.build (TLS, issued within minutes; the generated hosting URL works meanwhile and both are in get_app), hosting + database + auth, 100 emails in total, no custom domains, **locked after 7 days** until the owner goes live on a plan (data kept 30 days). The first testing app needs no card. To run up to 3 at once the owner verifies a card once: verify_card gives the URL ($1 authorization, released, nothing charged). get_trial_eligibility tells you the current count and limit; a testing_limit answer names what the owner must do.
- **Live**: create_checkout {appId, plan: "launch" | "grow" | "scale"} and give the owner the Checkout URL. Going live keeps everything and unlocks custom domains and monthly email allowances.
- Or a paid plan: create_checkout {appId, plan: "launch" | "grow" | "scale"} and give the owner the Checkout URL.

| Plan | Price | Database | Hosting allowance | Emails | Custom domains |
|---|---|---|---|---|---|
| Testing | $0 for 7 days | micro | $2.40 of hosting | 100 total | no |
| Launch | $29/month | micro | $6.00 of hosting | 1000/month | yes |
| Grow | $79/month | small | $24.00 of hosting | 10000/month | yes |
| Scale | $199/month | medium | $72.00 of hosting | 50000/month | yes |

Provisioning starts automatically after payment or trial start. Poll get_app every 10 s until components report
ready (hosting, database, auth). Failures show as action_required or failed with a reason; resume_provisioning
retries after you fixed what it names. Provisioning takes about a minute.

## 4. What your app gets at runtime

Launchbase injects these environment variables into the app on every release (reserved, cannot be overridden):

- `SUPABASE_URL`, `SUPABASE_ANON_KEY` and `NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY` — the app's own Supabase (Postgres + Auth). Use the Supabase client with the anon key; **no service-role or privileged key is ever injected**, so every table your app reads or writes needs row-level security policies.
- `APP_ORIGIN` — the public origin (generated URL, later the custom domain). Use it for redirects and links; Supabase Auth's site URL and redirect list are kept in sync by Launchbase.
- `PORT` — listen on 0.0.0.0:$PORT.

Anything else your app needs (API keys the owner gives you, feature flags): set_runtime_variables {appId, variables:
{"NAME": "value"}}. UPPER_SNAKE_CASE, values never read back, effective on the next release. Names starting with LB_ or
RAILWAY_ and the reserved names above are refused.

**Database schema.** You own the schema. Apply versioned migrations with apply_database_migration {appId, name, sql}
(name like 0001_create_notes; idempotent by name; roles/schemas/privileges are refused). Always:

    create table public.notes (id bigint generated always as identity primary key, owner uuid not null references auth.users(id) on delete cascade, title text not null, created_at timestamptz not null default now());
    alter table public.notes enable row level security;
    create policy "owners read" on public.notes for select using (auth.uid() = owner);
    create policy "owners write" on public.notes for insert with check (auth.uid() = owner);

Inspect with query_database {appId, sql} (read-only, one statement, capped rows; rows may hold end-user data, keep
them out of files). list_database_migrations shows what is applied. "starter" apps already have a base schema.

**User accounts.** Supabase Auth on the same app: email + password, magic links and password recovery through the
Supabase client with the anon key; confirmation emails come from Supabase for the app's users. `auth.uid()` in
policies ties rows to users.

## 5. Deploy

The contract, from GET https://launchbase.build/api/v1/guide:

- **payments**: Read /docs/payments.md and get_payment_guide. manage_app_payments configures curated test/live billing. configure_app_runtime injects LB_RUNTIME_URL/TOKEN for backend /payments/{action} calls. Derive customerRef from authenticated sessions; use stable requestId, confirm paid changes, check access.get. Managed mode needs no Stripe secret key; live verification is owner-only. BYOK owns its direct Stripe integration.
- **port**: Listen on 0.0.0.0:$PORT (3000). Never bind localhost only.
- **health**: options.healthcheckPath defaults to /. It must return HTTP 200 without authentication; APIs should expose a dedicated endpoint.
- **node**: package.json required. npm with package-lock.json for reproducible installs. node profile needs scripts.start; next runs next start; vite builds dist and serves SPA routes.
- **python**: requirements.txt required. Use a Procfile web command or options.startCommand, e.g. uvicorn main:app --host 0.0.0.0 --port $PORT or gunicorn app:app --bind 0.0.0.0:$PORT. Python 3.12.
- **docker**: Dockerfile escape hatch for other Linux stacks: install dependencies, listen on $PORT, provide CMD.
- **files**: Relative paths, one service root. Text files as UTF-8; images, fonts, PDF and MP4/WebM video as binary. Maximum 2000 files / 20 MB decoded. Exclude .env, .git, node_modules, caches and build output. Never include provider master keys.
- **storage**: Store end-user uploads and generated documents in Launchbase Files, never permanently in the container filesystem. Bundled assets and temporary processing may use local files. Keep file IDs and ownership in the database; authenticate every CRUD operation. Replace files by uploading a new immutable file, switching the database reference, then deleting the old file. Enable storage, configure runtime access, use /runtime/v1/files server-side. Read /docs/files-tasks.md.
- **cdn**: Optional CDN on active paid app plans: configure_cdn or PUT /api/v1/apps/{appId}/cdn. HTML never caches; public assets can cache. Private responses require Cache-Control: private, no-store even with cookies. Read /docs/cdn.md.
- **recovery**: restore_release redeploys old code, not data. get_recovery lists available database backups (Grow/Scale) and point-in-time ranges (Scale with the optional $129/month add-on). restore_database requires acknowledgeDataLoss:true and a stable requestId; agents can execute directly. It replaces newer database/auth changes, pauses hosting, and returns jobId to poll. Files and external payments are unchanged. Read /docs/recovery.md.
- **runtime**: Managed database/auth wiring provides SUPABASE_URL, SUPABASE_ANON_KEY (and NEXT_PUBLIC_ variants), APP_ORIGIN and PORT. No privileged keys are ever injected.

Options you can pass with a source: profile (static | vite | next | node | python | docker), startCommand, buildCommand,
healthcheckPath. Detection is automatic from the files; set profile only to force it.

1. Build and test the app locally. Read /docs/deploy.md for packaging rules. Optional preflight: node launchbase.mjs validate ./my-app --app APP_ID uploads a private source and validates it without starting a release.
2. Default: read https://launchbase.build/docs/deploy.md, download https://launchbase.build/launchbase.mjs outside the app folder and run node launchbase.mjs deploy ./my-app --app APP_ID with LAUNCHBASE_TOKEN privately or --credential-file FILE. The helper handles upload, validation and release creation. Never build a JSON file map for a real codebase. All upload paths share the 20 MiB / 2000-file limit. Exit 2: repeat the same command to follow the same release.
3. The helper creates the release and saves its IDs in .launchbase/. Repeat the same command on exit 2 to continue. Do not also call create_release or generate a new requestId while the helper is tracking a release.
4. Watch queued → uploading → building → starting → health_checking → live, or failed. The previous release stays live until the new one passes its health check. Use get_release for independent status checks.
5. Exit 1: inspect the printed diagnostics and build/runtime logs, fix the code or options, then repeat deploy. Changed source creates a new release. Use get_release_logs with before to retrieve more provider-retained logs. Typical causes: binding to localhost instead of 0.0.0.0:$PORT, missing start script, health path not returning 200 without auth, lockfile missing, build output not served.
6. The URL is in get_app (publicUrl). After a successful deploy, capture its public landing page without signing in and call set_app_preview {appId, capturedUrl: publicUrl, imageBase64: PNG base64}. Use a 1200×750 viewport, at most 1 MB. This updates the app card; never include private customer data. Rollback: restore_release {appId, releaseId, requestId} brings a previous release back (source only; database changes are not reverted).

Updating the app later is the same: new source, new release. Or connect GitHub: connect_github {appId, repository,
branch, token} with the owner's fine-grained token (Contents read, Metadata read, Webhooks read/write on that repo; ask,
never invent). Pushes to the branch then deploy automatically; deploy_from_github {appId} deploys now; get_repo
shows the state.

Limits in this release:
- One web service plus up to five background workers or scheduled tasks per app. Use deploy_task or POST /apps/{id}/tasks. Workers share Hosting capacity. Volumes and special hardware are not supported.
- Build success plus an HTTP 200 health check is verified; business logic needs your own tests.
- Provider log retention and ingestion delay apply; refresh logs after a failure and paginate with before.
- Restoring a release restores source only; database changes are not reverted.

## 6. Email

Managed sending works on paid plans from <app-slug>@launchbase.build (get_email shows the sender and counts;
testing apps have 100 emails in total). Send with send_email {appId, to, subject, text
or html, idempotencyKey}. Your running app sends through the same endpoint:

    POST https://launchbase.build/api/v1/apps/<appId>/email/send
    Authorization: Bearer <an app-bound credential with deploy:write>   (store it with set_runtime_variables as e.g. LAUNCHBASE_TOKEN)
    {"to": "…", "subject": "…", "text": "…", "idempotencyKey": "order-123-receipt"}

Or connect the owner's own Resend account: connect_email_byok {appId, apiKey, fromAddress} (their domain, their
price). Suppressed recipients and exhausted allowances are reported in the response, never silently dropped.

## 7. Payments for the owner's customers

- Read /docs/payments.md or get_payment_guide before building billing. manage_app_payments {appId, action, input} manages test/live plans, customers, checkout, saved cards, subscriptions, invoices, transactions, refunds, events and paid-access checks. Start with action:onboard and input:{mode:test}. Live activation returns an owner verification link. No Stripe secret key is required for managed payments.
- configure_app_runtime injects LB_RUNTIME_URL and LB_RUNTIME_TOKEN; the app backend calls /runtime/v1/payments/{action}. Derive customerRef from its authenticated session, never browser input. Use stable requestId values and obtain explicit confirmation before paid subscription changes/refunds. Only customer-specific client secrets and publishable keys reach the browser. BYOK is a direct Stripe integration with app-owned webhooks and access logic.

## 8. Domain

- Owned domain (paid plans): attach_domain {appId, domain} → give the owner the TXT record → verify_domain → give the owner the DNS records → refresh_domain until dnsOk and tls is issued → create one new release so the app uses the new APP_ORIGIN.
- Buy one: quote_domains {domains} shows live availability and price (registrar price, no markup). If the balance is short, create_topup {amountMinor} and give the owner the URL. Then call authorize_domain_purchase {appId, domain} for an owner checkout link. The owner enters registrant details and confirms the price in Launchbase; the agent cannot complete the purchase. DNS and TLS are configured automatically; poll list_domains / refresh_domain. Registrations are non-refundable.

## 9. Own accounts, usage, leaving

- Before provisioning on a paid plan, the owner can bring their Railway workspace (connect_hosting_byok) and/or Supabase organization (connect_database_byok): the app is created inside their accounts at the same plan price. get_byok shows the state.
- get_usage: Hosting, Database and Emails capacity, with measurement freshness and an upgrade URL. No automatic infrastructure overages. If usage_blocked, the owner reviews a whole-app upgrade; agents cannot authorize payment.
- Second product: create_app again (within the owner's plan limits); you are then working in it. To go back, switch_app (no owner approval).
- get_migration_plan and request_export give the owner everything to leave (source, database dump, users, guides).

## 10. A complete first run, in order

1. connect (or signup) → credential. 2. recommend_app → review_app_config → create_app. 3. verify_card → start_trial (or create_checkout). 4. Poll get_app until ready. 5. apply_database_migration for the schema (RLS on). 6. Follow /docs/deploy.md: deploy the local folder with /launchbase.mjs; repeat on exit 2, fix the reported build/runtime errors on exit 1. 7. Optional: send_email test, start_payments_connect, attach_domain or quote_domains → authorize_domain_purchase. 8. Report the live URL and every owner action still open.

## Tool reference (generated from the server; every tool has a JSON twin under /api/v1)

### Access and identity

- `get_guide` — Deployment contract, supported stacks, limits, plan and regions. Read before building.
- `whoami` — Identity of this credential: account, scopes, and the app you are working in (activeApp). You act in one app at a time.
- `draft_status` — Agent-first: current draft, proposed configuration, claim link and whether the owner has claimed it.
- `draft_update` {name, description, ownerEmail} — Agent-first: adjust the proposed name, description, configuration or owner email before the owner claims.
- `draft_redeem` — Agent-first: after the owner claimed the draft, exchange this draft session once for an app-bound credential.

### Plan the stack

- `get_byok` {appId} — Which of hosting/database run in the owner's own Railway workspace / Supabase organization for an app (never the tokens), and whether it can still be changed (before provisioning only).
- `connect_hosting_byok` {appId, token, workspaceId} — Before provisioning, connect the owner's Railway account token so the app is created in their workspace (they pay Railway directly; the plan price is unchanged). Ask the owner for the token; never invent one. Paid plans only.
- `connect_database_byok` {appId, accessToken, organization} — Before provisioning, connect the owner's Supabase personal access token (sbp_…) so the database and auth app is created in their organization. Ask the owner for the token; never invent one. Paid plans only.
- `disconnect_byok` {appId, component} — Back to Launchbase-managed hosting or database (before provisioning only); the stored token is retired.
- `recommend_app` {description} — Propose components from a description. Nothing is created or authorized.
- `review_app_config` {payg, capMinor} — Validate a configuration and show what would be created and charged.
- `list_apps` — Every app in the workspace with name, description, status and which one you are working in (active). You act in one app at a time; see switch_app.
- `switch_app` {appId} — Move your work to another app. An app you created is current already; with only one app it becomes current at once; otherwise call this tool to select an app immediately. No owner approval is needed. App-bound credentials cannot switch.
- `get_app` {appId} — App with components, services, budget, required actions and public URL.
- `create_app` {name, description, budget, paygEnabled, monthlyCapMinor} — Create a draft app (no provider resources yet). Requires app:write.
- `rename_app` {appId, name} — Rename an app without changing its URL, code, services or billing. Requires app:write.
- `update_app` {appId, name, confirm} — Change name/components. Live apps return an impact summary until confirm=true.
- `set_budget` {appId, paygEnabled, monthlyCapMinor, exhaustionAction} — Legacy budget endpoint: automatic infrastructure overages are disabled; paygEnabled must be false. Upgrade the app plan for capacity. Requires billing:write.
- `delete_app` {appId, immediate} — Ask the owner to delete an app. Agents never delete: this returns a URL where the owner confirms by typing the app name (draft removed at once; provisioned resources retired after 30 days). Call again to see the status.

### Pay and provision

- `resume_provisioning` {appId} — Retry provisioning after a failure (needs an active plan). Requires spending:execute.
- `get_usage` {appId} — Four app usage categories: Hosting, Database, Files, Emails. Each reports its limiting resource, freshness, reset date and upgrade URL. Unknown usage is not zero. Fixed plans have no automatic infrastructure overage charges.
- `get_billing` — Plan, subscriptions, entitlements, prepaid balance and recent ledger for this account.
- `verify_card` — Optional card check that lets the owner run up to 3 testing apps at once (the first needs no card): Stripe places a $1 authorization and releases it (nothing charged). Returns an action_required URL for the owner; poll get_billing → card.verified. Requires billing:write.
- `start_trial` {appId} — Deploy a draft as a free testing app: public URL, hosting + database + auth provisioned now, 100 emails in total, no custom domains, locked after 7 days until the owner goes live on a plan. One concurrent trial without a verified card; three with one. If blocked, show the owner card link or ask which trial to pause, poll pause completion, then retry. Free start needs app:write; card entry and paid plans require owner action. Deadlines never restart.
- `get_trial_eligibility` {appId} — Whether an app can be deployed as a free testing app (active count, limit, card state) and why not otherwise.
- `create_checkout` {appId, plan} — Start a paid plan subscription for an app (launch $29, grow $79, scale $199 per month; provisional allowances). Also reviews a higher paid plan, upgrades a trial or resumes a paused app. Paid upgrades require owner confirmation of Stripe proration. Returns an action_required URL the account owner must complete in a browser; provisioning or resume starts automatically after payment is confirmed.
- `create_topup` {amountMinor} — Create a prepaid top-up Checkout (owner completes it in a browser).

### Database

- `apply_database_migration` {appId, name, sql} — Apply one named SQL migration to the app database (the Postgres your app reaches with SUPABASE_URL). Versioned and idempotent by name; create tables, indexes, functions, policies; always enable row-level security and reference auth.users for per-user data. Roles, schemas and privileges are managed by Launchbase and refused. Requires database:write and a ready database.
- `list_database_migrations` {appId} — Migrations applied to the app database, in order.
- `query_database` {appId, sql, limit} — Run one read-only SQL statement on the app database for inspection (schema, counts, a few rows; capped at 200 rows). Rows can contain end-user data: never write them to files or logs. Requires database:write.

### Deploy

- `set_app_preview` {appId, capturedUrl} — Update the private dashboard card with a PNG screenshot of the current publicUrl. Capture the public landing page without signing in after a successful deploy; max 1 MB, 2400x1600. Never include customer data.
- `get_repo` {appId} — The GitHub repository connected to an app (owner/repo, branch, auto-deploy, last commit and release, webhook URL).
- `connect_github` {appId, repository, branch, token, rootDir, autoDeploy} — Connect a GitHub repository with the owner's fine-grained token (Contents read, Metadata read, Webhooks read/write on that repo). Installs a push webhook: pushes to the branch become releases. The token is stored encrypted and never returned. Ask the owner for the token; never invent one.
- `disconnect_github` {appId} — Remove the GitHub connection and its webhook; releases already made stay.
- `deploy_from_github` {appId, ref} — Deploy the connected branch (or a ref) now: downloads the commit, verifies it and creates a release with requestId github-<sha>. Poll get_release / list_releases.
- `validate_source` {appId} — Check files and options without uploading: stack detection, build/start/health and errors with next actions.
- `get_deploy_instructions` {appId} — Default deployment path: download a standalone Node helper and deploy the local app folder. No JSON file map or Railway credential needed. Remote MCP cannot read your local filesystem.
- `upload_source` {appId} — Small inline examples only: upload a programmatically constructed files map. For normal apps use get_deploy_instructions and the local-folder helper; never hand-escape source code. All paths share the 2000-file / 20 MiB decoded limit.
- `create_upload_intent` {appId, sizeBytes, fileCount} — Get a signed URL to PUT raw gzip tar bytes, then finalize. Used automatically by the folder helper. Maximum 2000 files / 20 MiB decoded, regardless of compression.
- `finalize_source` {appId, sourceId} — Verify an uploaded tarball and run preflight.
- `create_release` {appId, sourceId, requestId, message} — Deploy a verified source version. requestId makes retries safe; reuse it only for the identical payload.
- `get_release` {appId, releaseId} — Release status, phase, provider deployment id, health and diagnostics.
- `list_releases` {appId} — Releases of an app, newest first.
- `get_release_logs` {appId, releaseId, kind, before} — Build or runtime logs for the exact deployment (500 per page; use nextBefore as before for older pages). Secrets are redacted.
- `restore_release` {appId, releaseId, requestId} — Deploy an earlier release's source again as a new release (source only; the database is not rolled back).
- `cancel_release` {appId, releaseId} — Cancel an in-progress release.
- `set_runtime_variables` {appId, variables} — Set environment variables for the app app (values never read back; effective on the next release). Requires secrets:write.
- `list_runtime_variables` {appId} — Names of managed and custom runtime variables (never values).

### Files

- `configure_app_runtime` {appId} — Inject server-only LB_RUNTIME_URL and LB_RUNTIME_TOKEN. Redeploy to activate. Never expose the token in browser code.
- `get_file_storage` {appId} — File allowance, bytes used and whether uploads are enabled.
- `configure_file_storage` {appId, enabled} — Enable private file uploads or pause new uploads without deleting files. Deploy hosting first. Call configure_app_runtime next to inject LB_RUNTIME_TOKEN. Read /docs/files-tasks.
- `list_files` {appId, after} — List app files, 100 per page. after is nextAfter from the previous page.
- `create_file_upload` {appId, name, contentType, sizeBytes, requestId} — Reserve file space and get a private signed PUT URL. Calculate SHA-256 first. PUT exact file bytes using returned headers, then finalize_file. Never put LB_RUNTIME_TOKEN in the browser.
- `finalize_file` {appId, fileId} — Verify size and checksum after upload. Returns the file ID to save in the app database with its user ownership.
- `download_file` {appId, fileId} — Get a private download URL valid for five minutes. Do not publish links to customer files.
- `delete_file` {appId, fileId} — Delete one file after confirming that the user intends its removal. File capacity is freed after any active upload URL expires.

### CDN

- `get_cdn` {appId} — Read app CDN settings and paid-plan eligibility.
- `configure_cdn` {appId, enabled} — Enable or disable CDN on a paid app. Read /docs/cdn.md first. HTML stays uncached; mark every private response Cache-Control: private, no-store. Cookies alone do not bypass caching.
- `purge_cdn` {appId} — Clear all cached assets for this app. Successful deployments clear them automatically.

### Recovery

- `configure_recovery_addon` {appId, enabled} — Request owner browser approval to enable or disable the optional Scale recovery add-on at 129 USD/month. This tool never charges directly. Open or give the returned URL to the owner.
- `get_recovery` {appId} — List actual database backups, the available point-in-time window and restore history. Backup restore requires Grow or Scale; point-in-time requires Scale with the active $129/month add-on. Read /docs/recovery.md.
- `continue_database_restore` {appId, restoreId} — Resume monitoring or app restart after a failed recovery job. Does not replay an uncertain destructive submission; those require support verification. Use the existing restore ID.
- `restore_database` {appId, kind, backupId, time, requestId, acknowledgeDataLoss} — Restore this app database to an available backup or time. Destructive: newer records and auth changes are replaced; code, file bytes and external payments are unchanged. Agents can execute directly with acknowledgeDataLoss:true. Use a stable requestId, poll jobId, never submit a second restore while the first is uncertain. Read /docs/recovery.md first.

### Background tasks

- `list_tasks` {appId} — Background workers and scheduled tasks for the current app.
- `deploy_task` {appId, name, sourceId, startCommand, schedule, requestId} — Deploy a worker or UTC scheduled task from an existing verified sourceId. Uses the app Hosting allowance; no public URL. Supply startCommand such as python worker.py. Omit schedule for a persistent worker, or use 0 9 * * * for daily 09:00 UTC. Poll get_release and inspect logs. Implement durable jobs/retries in your database; schedules skip overlapping runs. Task code must exit after scheduled work.
- `pause_task` {appId, serviceId} — Stop a background task and its schedule. Confirm interruption with the user first.
- `resume_task` {appId, serviceId} — Resume a manually paused task within this app Hosting allowance.

### Domains

- `list_domains` {appId} — Custom domains attached to an app with verification, DNS and TLS state, plus purchase authorizations. Requires app:read.
- `attach_domain` {appId, domain} — Start attaching a domain the customer controls: returns the TXT record that proves ownership. Requires app:write and provisioned hosting.
- `verify_domain` {appId, domain} — Check the ownership TXT record; on success creates the hosting route and returns the DNS records the customer must add. Requires app:write.
- `refresh_domain` {appId, domain} — Re-check DNS propagation and TLS for an attached domain; when live, the app origin and auth redirects are updated (generated URL stays valid). Requires app:write.
- `detach_domain` {appId, domain} — Remove a custom domain from the app (DNS at the registrar is not touched).
- `quote_domains` {domains} — Live registrar availability and prices (registration and renewal, with the Launchbase margin) for up to 10 names. Never registers anything; says whether buying through Launchbase is enabled.
- `authorize_domain_purchase` {appId, domain, quotedRegistrationMinor, years, autoRenew, registrant, firstName, lastName, email, phone, organization, city, state, postalCode, country} — Request owner checkout for a domain. Open the returned absolute URL in a browser or give it to the user. Missing cards return card_required with a card-setup URL that returns to this domain. Agents cannot pay or register it. The owner confirms the current price and registrant details in Launchbase. Requires domains:purchase.

### Email

- `get_email` {appId} — Email sender status, monthly counts, suppressions and recent messages for an app.
- `connect_email_byok` {appId, apiKey, fromAddress} — Connect the customer's own Resend account (key validated and stored encrypted, never shown back). Requires secrets:write.
- `send_email` {appId, to, subject, text, html, replyTo, idempotencyKey} — Send one transactional email through the app sender. idempotencyKey makes retries safe; suppressed recipients and exhausted allowances are reported, never silently dropped.

### Payments for your customers

- `get_payment_guide` — Read before building customer billing. Curated managed payment actions, examples, runtime contract, onboarding and BYOK boundaries. These are customer app payments, not Launchbase plan purchases.
- `manage_app_payments` {appId, action, input} — Manage customer billing through curated actions. Read get_payment_guide first. Includes plans, customers, checkout, cards, subscriptions, transactions, invoices, refunds and events. Confirm customer/owner intent before changing subscriptions or refunding. Agents receive an owner link for onboarding; account.session is owner-only. BYOK bypasses this API.
- `get_payments` {appId} — Customer-payments connection state (Connect or BYOK), Stripe verification requirements and guidance.
- `start_payments_connect` {appId, country, businessType} — Compatibility onboarding tool. Prefer get_payment_guide then manage_app_payments with explicit mode:test or live. Agents create test accounts; owners activate real payments. Requires app:write.
- `connect_payments_byok` {appId, secretKey, publishableKey} — Store the customer's own Stripe key after validating it with Stripe (never shown back). Requires secrets:write.

### Jobs, exports, leaving

- `list_jobs` {appId} — Background jobs (provisioning, deployments) with status and next action.
- `get_job` {jobId} — One job with steps and sanitized errors.
- `get_migration_plan` {appId} — Machine-readable plan for leaving Launchbase: per component what is exportable, ordered steps with checks and rollback, non-exportable secrets and downtime expectations. Requires export:read (owner).
- `request_export` {appId, source, database, auth} — Start an asynchronous full export (inventory, source snapshots, portable database dump with checksums, auth users, email metadata, guides) into private storage; poll get_export until ready. Requires export:read (owner).
- `get_export` {appId, exportId} — Status, manifest and part checksums of an export; when ready, includes short-lived signed download URLs for every part. Requires export:read.
- `list_exports` {appId} — Recent exports of an app. Requires export:read.
