PricingPrivacyTermsSign in

Connect your agent › The agent's guide

apphaven — guide for AI agents

apphaven hosts web apps for people who aren't developers. You write the app's files through these tools; apphaven gives it a real backend (database, sign-in, access rules) and hosts it.

  • Frontend: your code. The usual shape is a Vite project (React, Vue, Svelte, Tailwind…) made installable, so the user has their app on their phone's home screen like any other app (see "Make it installable" below). Plain HTML/CSS/JS pages suit a website: content that loads fast and that search engines read. The two combine: pages next to the app. No server-side rendering, no Node server code.
  • Backend: no server code. You describe data, access rules and operations in YAML files in apphaven/. apphaven turns them into a database and a REST API.
  • Accounts: every app gets email + password sign-in, address confirmation and password reset for its users, built in. Email sign-in links are optional (auth.magic_link). No external auth or database provider setup is needed.

Workflow

  1. app_status (no app given lists them) / create_app — one app per project. The user's apps are private to them. An account holds at most five apps that aren't live yet (never published, or dark since their plan ended); create_app says so when it's full. Before starting a new app, check app_status: if the user's idea fits an app they already have, build there, and if they're at the limit, ask them to delete one they no longer need from the console, or publish one. Rarely, when Apphaven is near its capacity, create_app asks to try again tomorrow; nothing else is affected.

  2. get_docs — read config before writing YAML, and sdk before writing frontend data code (and files when the app takes photos or documents from its users).

  3. write_files — write the frontend and the apphaven/*.yaml config. Send several files per call. For small changes to an existing file, edit_file (exact find-and-replace) instead of resending it.

  4. validate — checks config and shows the database changes a deploy would make. Fix every error.

  5. deploy_preview — builds, migrates the preview database, and publishes to the preview URL. Read the summary: it lists warnings and database changes.

    Both also scan the files and refuse to deploy (and going live refuses too) when they find a secret: API keys, tokens, connection strings, private keys, .env files. Everything in the app's files is served to every visitor, so there is nowhere safe for a secret in a frontend; remove it and tell the user to revoke it. (Server-side connectors for things like payments are coming; until then, don't call third-party APIs that need a secret.) The scan also stops http:// scripts and public access to fields like password or ssn, and warns about eval, HTML built from data (XSS), public personal fields, and update: true / delete: true. A documented placeholder can be accepted with scan-ignore on its line.

  6. Test it yourself before handing it over:

    • test_api (reads) and test_api_write (POST, PATCH, PUT, DELETE) — call the API as test users (as: "alice", as: "bob") or signed out, to check each access rule from both sides (alice can't read bob's data, bob can't change alice's, a visitor can submit the form, etc.).
    • view_page — screenshot a page (optionally signed in as a test user) with console errors and failed requests. Check the main screens on a phone (device: phone, tablet, desktop; full_page: true for the whole page). Add steps to use the page like a person first ({"click_text": "Add task"}, {"fill": "#title", "value": "Milk"}, {"click": "form button"}, {"wait_for": ".task"}, {"hover": ".card"}, {"press": "Tab"}, {"scroll_to": "#footer"}), so forms and buttons get tested, not just first screens. "Are you sure?" boxes are answered OK (dialogs: "dismiss" for Cancel). One page at a time: a second screenshot asked for while one is still opening can be refused; wait for the first, then ask again.
    • query_data / change_data (insert, update, delete), get_logs (recent API errors).
    • Test ordinary sign-up and address confirmation separately, without as: test users are already confirmed. Follow "Testing sign-in in preview" in get_docs topic sdk. Fix and redeploy as often as needed, then give the user the preview URL (the url in the deploy_preview result) to open and try; they come back to the chat with what should change.
  7. Going live is the user's step. The first time, they publish from the dashboard (publish_url in app_status; give them that link), where they choose whether the live app starts with the test version's accounts and data. After that, deploy_live pushes later changes to the live app when the user asks. Changes that would delete live data need the user's approval in the dashboard; tell them so. Publishing needs an active plan or a publishing allowance: app_status explains when the app lacks that access. Explain the current availability factually; building and testing remain available without publishing. Billing belongs to the owner in Apphaven. Do not recommend a plan, promote an upgrade or start checkout through the connector.

  8. When a piece of work is done, send_feedback is a courtesy, never a requirement: a short note to the Apphaven team on what blocked you, broke, confused, would help, or worked well. It shapes what you can do for your users here. Nothing is answered in the conversation.

What tool permissions mean

Hosts may ask for permission according to everything a tool can do, even when this call only takes a screenshot or requests a dry run. Permission to work in preview does not authorize publishing, changing live data, or sending information to someone else.

  • write_files, edit_file and restore_version can overwrite or remove workspace files.
  • deploy_preview replaces the test version and can remove preview data. npm builds run the app's build script and can contact external services; the test version remains private.
  • test_api uses GET, but as can create/confirm an account and sign it in, and auth links can confirm accounts or consume a sign-in token. It is not a read-only tool. test_api_write can also overwrite/delete data, upload sample files and trigger preview mail. Only the owner's own address is eligible for delivery from preview.
  • view_page runs the app and its scripts in a browser. Its actions can change/delete data, upload files, send mail and follow links or contact services outside Apphaven.
  • deploy_live changes a publicly available app. change_data and manage_users can remove data or accounts in the selected environment; their existing production safeguards apply.
  • send_feedback shares a note with the Apphaven team outside the owner's workspace. These tools cannot recall it. Never include personal details or the app's data.

The retrieval tools (get_docs, app_status, read_files, query_data, list_users, get_logs, read_emails) and validate are read-only. create_app adds an app without replacing an existing one. Tool permission labels do not replace the owner's authorization.

Your own domain

The owner can put the live app on an address they own, like www.theirshop.com. They enter it in the console's Settings (not you: it's their domain); from then on app_status shows the domain's state, the one record to add, and the reason when it's stuck, and the owner may paste you a request to help. Walk them through it:

  1. Ask where the domain is registered if you don't know (GoDaddy, Namecheap, Squarespace, Cloudflare, Wix, IONOS…). The record goes in that domain's DNS settings there, nowhere else.
  2. Give the record exactly as app_status shows it: a CNAME, name www.theirshop.com (some providers want only www), pointing to <slug>.customers.onapphaven.com. Say which screen it's on at their provider ("DNS", "DNS records", "Manage DNS") and that any existing record for www must go. Nothing else changes; no A record, no nameserver change. If their DNS is at Cloudflare, the record must be DNS only (grey cloud): a proxied record hides the CNAME and app_status then reports an address record instead.
  3. The bare domain (theirshop.com) can't point here directly, so people who type it need a forwarding rule at the same provider, from theirshop.com to https://www.theirshop.com (GoDaddy and Squarespace call it Forwarding, Namecheap "Redirect Domain", Cloudflare a Redirect Rule). Suggest it once the www address is live.
  4. Check, don't promise. app_status re-checks the record each time you call it (at most once a minute): waiting_dns means the record isn't visible yet (minutes usually, up to a day at some providers; the detail says if it points somewhere else), issuing means it's in place and the certificate is coming, active means done. Then the live address is the domain, and the onapphaven.com address sends people there.

Test version and sign-in

There are three identities to keep separate:

Identity What it gives access to
The owner's Apphaven account The dashboard and the private preview. Signing in to Apphaven opens the preview; it does not sign them in inside their app.
An account inside the app The app's features, data and roles. The owner signs up here separately, even if they use the same email address as their Apphaven account. Passwords and sessions are separate.
A test user (as: "alice") An app account the tools create and sign in automatically in preview. Its address is already confirmed, so it checks authenticated screens and access rules, but does not exercise ordinary sign-up or confirmation.

Preview and production have separate databases and separate user accounts. Preview starts as a copy of production (change_data with action: reset_preview refreshes it). Reset prepares a separate copy, removes copied passwords, provider tokens, sessions and verification tokens, then applies the test version's schema before switching it over. If preparation fails, the existing test version stays usable with its data unchanged. A message saying the reset was interrupted means you can retry the same action to finish it safely. Sign in afterward with fresh test users or new sign-ups; copied production accounts cannot sign in with their live passwords.

Handing the test version to the owner. Give them the preview URL and explain the steps: open it while signed in to Apphaven, sign up inside the app using their Apphaven account's email address, open its confirmation email, then return to the app and sign in. Only that address is eligible for real email delivery from preview; other addresses' mail is captured for you to inspect with read_emails. Check the mail status if it does not arrive. The owner chooses their own app password; you do not need it.

The owner is not automatically an app admin. When the app has an admin/organiser role, use manage_users (action: set_role) on their app account after they sign up, so they can try their own controls. Do the same on production after publishing (unless you launch with preview data, below).

Cleaning up before going live. manage_users with action: delete removes leftover test accounts (records that need them go too: check what it reports). Delete made-up sample records too. When the owner publishes from the dashboard, they can bring the test version's accounts and data along (same passwords and roles; @apphaven.test users are left out), so make sure what's there is real. Once the live app has its own data, the two databases stay separate.

The preview is private

Only the app's owner can open the preview, signed in to apphaven: the preview URL sends anyone else to a sign-in page, and its API answers 401 preview_locked. So a preview link is for the owner to look at their own app, not something to hand to testers or members; when other people should use the app, the owner publishes it. Your test_api and view_page calls get in automatically, and the test users you create sign in inside the preview as before.

The confirmation and reset links in the preview's sign-in mail work on any device, because the owner often opens their own mail on a phone that isn't signed in to Apphaven: each carries a pass for that one link. A confirmation link confirms the address there, and the phone then says to go back to the test version and sign in. A reset link opens Apphaven's built-in page (/_apphaven/reset-password, the SDK's default) and the new password is set there; a reset page of the app's own is an ordinary preview page, so it asks the owner to sign in to Apphaven first. A sign-in link (magic link), or any other address the phone opens, asks them to sign in to Apphaven first too.

When sign-in fails or another method is requested

Read the failing endpoint, response code and message before changing anything: preview_locked is the private preview gate, an unconfirmed address needs its confirmation link, and a model's forbidden response may be an access rule. A 401 or 403 alone does not identify the cause. The sdk docs have the troubleshooting steps and a complete sign-up test.

Apps currently support email/password and optional email sign-in links. Google or Apple sign-in for an app is not provided; signing in to the Apphaven platform with Google is separate. Explain that capability gap and offer the supported flow. Keep the working Apphaven auth and data in place when diagnosing preview restrictions. Moving to an external provider is a separate choice for the owner, with setup and data implications to explain first; placeholder provider settings do not make a sign-in option work. You can report the missing capability through send_feedback.

Files

package.json          ← a Vite project: apphaven builds it and serves dist/
vite.config.js, index.html, src/...
public/               ← files served as-is next to the app (plain pages, icons, robots.txt)
apphaven/app.yaml     ← backend config (all apphaven/*.yaml files are merged)
  • Vite/npm: if package.json has a build script, apphaven runs npm install and npm run build and serves dist/ (change with frontend.output_dir). Don't upload node_modules. @apphaven/client is pre-installed. Unchanged frontend inputs can reuse the last successful build for up to an hour, including when only apphaven/ backend settings change. Editing frontend files (including a dependency lockfile) triggers a new build. An Apphaven platform update also invalidates saved builds.
  • Plain files, no build: with no package.json, every file except apphaven/ and dotfiles is served as-is (index.html, app.js, style.css…). The sdk is at /_apphaven/sdk.js.
  • Either way, /about serves about.html or about/index.html. Unknown paths fall back to index.html (set frontend.spa: false to get 404s instead; a 404.html is used if present).

Make it installable

An app the user opens every day belongs on their phone's home screen. A Vite app becomes one with vite-plugin-pwa: a manifest (name, colours, icons), a service worker that keeps the app opening instantly, and nothing else to set up; apphaven serves the manifest and the worker with the right headers. The recipe is in the examples (get_docs topic examples, "Installable"). Two rules keep it honest:

  • Use registerType: "autoUpdate" and keep /api and /_apphaven out of the worker's navigation fallback, exactly as the recipe shows. Then every deploy reaches installed phones on their next open, and sign-in and data never go through a cache.
  • Icons are square PNGs (192 and 512). Ask the user for their logo through the Add files page and make the icons from it, or draw a simple one; a made-up path is a blank icon.

Install from the live app, not the preview (the preview is private and signs the owner in each time). Tell the user how: on an iPhone, open the live address in Safari, Share, "Add to Home Screen"; on Android, the browser offers "Install app" or it's in the menu.

A website and an app together

A business usually wants both: pages that load fast and that search engines read (home, about, prices), and the app behind a sign-in. Put the pages in public/ as plain HTML (public/about.html is served at /about) next to the Vite app, which takes / or /app; one project, one deploy, one domain. Keep the pages' links to the app relative (/app) and the shared look in one CSS file.

Files from the user (logo, photos, documents)

These are the owner's own files for the app (its logo, the menu photos). Files the app's users send it (a profile photo, a receipt, a CV) are image and file fields instead: get_docs "files".

Don't ask the user to paste or attach files in the chat: chat attachments never reach you as bytes, and typing a picture out as base64 is slow and error-prone. The same goes for photos you find on the web: fetching a page gives you text, never the image's bytes, so write_files with base64 can't carry them (it's for small files you make yourself, like an SVG icon), and a made-up path is a broken image. Photos come from the user: send them to the app's Add files page (files_url in create_app / app_status, which is <dashboard>/apps/<id>/files?add=files):

I need your logo. Drop it in here: https://…/apps/app_123/files?add=files — I'll wire it in.

What they drop lands in the workspace under uploads/ (they can pick another folder), the test version is redeployed so the files are served at /uploads/<name> (in a Vite app too: Apphaven adds the owner's files to the built output at their own path, unless the build already serves that path; hidden paths are excluded). Whole-project imports are build inputs and do not get this extra serving step. Photos lose their location data on the way in. Your next tool result, whatever tool it is, ends with a note listing the new paths and image sizes. read_files shows them too, marked [added by the user]. Then reference them by path (<img src="/uploads/logo.png">) and deploy_preview.

For files you make yourself, write_files with encoding: base64 works, and a bad string gets an error naming the offset. Prefer SVG for icons and artwork: it's text, so nothing can be mistyped.

Email

Apps send email with no setup, from "<App name>" <<slug>@viaapphaven.com> (the test version adds +preview):

  • Sign-in mail is automatic: password reset (app.auth.requestPasswordReset), sign-in links (auth.magic_link: true, no passwords at all) and address confirmation. Every password sign-up confirms its address before it can sign in: signUp returns the user but no session, so show "check your email". Test users (@apphaven.test) are confirmed on creation in the preview; the live app refuses them. See the sdk docs.
  • Operations can email: email: { to, subject, template | body } on a create/update operation sends once per record, to the owner, the signed-in user, or addresses in the record (customer.email). Templates are HTML files in apphaven/emails/ with {{ placeholders }}. These are fully supported; sign-in messages use fixed Apphaven copy with the app's name. The owner's Mail page previews the deployed templates with sample details at phone and desktop sizes, without sending anything. See the config docs.
  • Preview delivers only to the owner's Apphaven account email address. Other recipients' mail is captured, which is expected: it is kept for inspection, not sent to their inboxes. Every message is kept in the app's mail log: read_emails to check its status, and with an id to read one rendered. Owner delivery can also fail or be limited; check the actual status. Use them the way you use view_page, and open the links they contain (the confirmation link included) with view_page to test the whole flow. Live apps send for real: up to 500 a day once established (100 a day for the first week, 250 until day 30), with a per-address limit for addresses nobody confirmed; they skip addresses that bounced or unsubscribed, and sending pauses by itself on a spam complaint or too many bounces (read_emails says so).
  • Not for marketing: one event, one message, to the people in it. Newsletters and campaigns need the owner's own email service.

The backend in one example

# apphaven/app.yaml
models:
  Task:
    fields:
      title: { type: string, required: true, max_length: 200 }
      done: { type: boolean, default: false }
      owner: { type: user, default: $user.id, readonly: true, required: true }
    access:
      read: owner == $user.id
      create: $user != null
      update: owner == $user.id
      delete: owner == $user.id
import { createClient } from "/_apphaven/sdk.js";      // Vite: from "@apphaven/client"
const app = createClient();
await app.auth.signUp({ email, password, name });   // then "check your email": no session until the link is opened
const { items } = await app.db.Task.list({ filter: "done == false", sort: "-created_at" });
await app.db.Task.create({ title: "Buy milk" });

Rules of thumb

  • Access is denied unless a rule allows it. Every model needs access rules, or the app can't touch it. Think about who may read/create/update/delete each model, and write it down.
  • For "things a user owns", use the owner pattern above (type: user, default: $user.id, readonly: true), so the creator is recorded and can't be faked.
  • Never put secrets or API keys in frontend files — everything there is public.
  • Keep the UI working when signed out: show a sign-in form, and handle 401/403 errors.
  • Renaming a field or model? Use renamed_from so data is kept. Removing one deletes its data.
  • Every deploy saves the files as a version: restore_version (the versions are in app_status) undoes a bad change (files only — data isn't rolled back).
  • The user is not technical: explain what you built in plain words, give them the preview URL, and don't ask them to edit code or YAML.
HomeDocsPricingPrivacyTermsSupportReport content A product of Efficacious, Inc.