Browse the docs
Reference
Reference
Every fuast CLI command and flag, deploys, tokens, inspection routes, the HTTP call API, the realtime protocol, and environment variables.
The quick contract for the fuast CLI and HTTP surface. For the code-level API see capsule API and database.
CLI
Run bunx @revnu/fuast <command>, or fuast <command> when it is installed. Every command that prints data accepts --json. fuast <command> --help (or fuast help <command>) prints one command's usage and options.
Targeting an app
Inspection commands (inspect, run, db, logs, storage) pick their target like this:
--port <n>: thefuast devserver on that port, even if a slug is also given.- A slug or app URL argument: the deployed app.
- Neither:
fuast devon port 3000 (orFUAST_PORT_DEV).
With a slug, the CLI talks only to the platform (FUAST_API), which forwards to the app, so your token never goes to the app's own origin. With an app URL, pass --inspect-token. They do not read fuast.json. To inspect a deployed app, pass its slug (the app value in fuast.json) or its URL.
An unclaimed preview is inspected the same way, by the slug deploy printed, on the machine that deployed it. The CLI finds the preview's edit token in ~/.fuast/previews.json and sends it instead of an account token. inspect, logs, db list|dump|export, jobs list, storage list, and run work. Commands that change an app outside its handlers (db import, storage put|delete, jobs cancel, run schedule, share, domains) need an owner: claim the app first.
Platform commands (share, users, domains, deploy archive|restore|terminate) take a slug argument or fall back to fuast.json in the current directory. deploy reads fuast.json from the capsule directory.
Create
fuast new <dir> [--template todo|guestbook|signature|approvals|live-demo] [--no-git]
fuast create <dir> ... alias of new
fuast init [--template ...] [--no-git] scaffold into the current directorynew writes the template, fuast-env.d.ts, AGENTS.md, CLAUDE.md, .gitignore, and tsconfig.json, then creates a git repository with a first commit unless you are inside one or pass --no-git. init refuses when the directory has files other than hidden files, README*, LICENSE*, AGENTS.md, and CLAUDE.md, and appends fuast's lines to existing AGENTS.md, CLAUDE.md, and .gitignore.
Develop
fuast dev [dir] [--port 3000] [--host 127.0.0.1] [--reset] [--no-typecheck] [--allow-host <name>]...
fuast run-many [dir] [--count 20] [--base-port 4000]
fuast check [dir] [--no-typecheck] [--json]
fuast build [dir] [--out artifact.json] [--no-typecheck] [--json]
fuast types [dir]
fuast templatesdevruns the app locally with hot reload for server and client, the same WebSocket protocol, database engine, limits, and inspection routes as hosted apps. Data and files persist in.fuast/dev.--resetwipes them.--host 0.0.0.0exposes the server, including its open inspection routes, to your network.--allow-host <name>accepts anotherHostheader (for a tunnel or a LAN name); repeat it per name.--no-typecheckturns off the type check after each save.run-manystarts--countindependent dev servers of the capsule on consecutive ports from--base-port, each with its own data, and prints their URLs. Ctrl-C stops them all.checkbuilds the capsule, runs the TypeScript checker, and verifies the contract (imports,capsule()keys, schema, cron expressions, favicon). Exit code 1 on any error. Run it before every deploy.check --no-typecheckskips only the type check.check --jsonprints{ ok: true, manifest, ... }or{ ok: false, error, code, diagnostics }. Each diagnostic hasfile,line,column,code,category,message, and a sourceframewhen the file is known.buildbuilds without deploying and prints bundle sizes.--outwrites one JSON artifact with the manifest, bundles, CSS, and favicon.typesrewritesfuast-env.d.ts(andtsconfig.jsonif missing) from the installed CLI. Run it after upgrading.templateslists the starter templates.
Local identities: ?fuast_user=<name> and ?fuast_guest=1 in the URL, or fuast auth as <name>. See auth.
Deploy
fuast deploy [dir] [--name "My App"] [--delete-table TABLE=COUNT]... [--no-typecheck] [--no-unclaimed] [--claim | --new] [--json]
fuast claim [dir]
fuast status [dir]
fuast deploy list
fuast deploy rollback <deployId>
fuast deploy archive [slug|deployId]
fuast deploy restore [slug|deployId]
fuast deploy terminate [slug|deployId] --yesdeployuploads the capsule source and.env.fuast.server. The platform type checks it (errors block the deploy unless--no-typecheck), builds it, describes it in a sandbox, applies schema changes, and makes it live.- Logged in (
fuast login, orFUAST_TOKENin CI), it deploys to your account and prints the app URL. The first deploy creates the app and writesfuast.json({"app": "<slug>"}). Commit it. - Not logged in, it deploys an unclaimed preview and prints a preview link, a claim link, and when the preview is deleted. See deploy without an account.
- Before uploading,
deployruns the same build, contract check, and type check asfuast checklocally, and stops on the first error. --namesets the app's display name on first deploy. Without it, the CLI usescapsule({ name }), then the directory name.deploy listprints each app you own with its status, live deploy id, and URL.--delete-table TABLE=COUNTconfirms dropping a populated table removed from the schema. Repeat it per table. See schema changes.- A rejected destructive change returns code
DESTRUCTIVE_SCHEMA_CHANGE(HTTP 409) withtables: [{ table, rowCount }]. - Every deploy is an immutable record with its source snapshot.
rollbackmakes an earlier deploy live again with the current data. It fails if that deploy's schema cannot accept the current rows. archivestops serving the app untilrestore.terminatestops it permanently. Both keep its data, logs, and records.
Deploy without an account
fuast deploy works before anyone has a fuast account. It puts the app online as an unclaimed preview and prints two links:
- The preview link (
https://preview-<random>.<apps domain>/?fuast_preview=…) shows the app, after a fuast page that says what a preview is. Anyone who has the link can look. It cannot claim the app. - The claim link (
https://fuast.com/claim/…) makes the app someone's. They open it, sign in with a verified account, and choose Claim. It works once.
What the CLI does:
- It saves
{ api, slug, editToken, expiresAt, url, claimUrl }in~/.fuast/previews.json(mode 600), under the real path of the project folder. The edit token lets this machine redeploy and inspect the preview. The CLI never prints it and sends it only to the platform that issued it. Nothing is written to the project folder:fuast.jsonis for apps that have an owner. - It never reads a credential from the project folder. A repository can ship any file, so a
.fuast/unclaimed.jsonin the working tree is ignored, with the warningignoring .fuast/unclaimed.json in this folder. A folder this machine has no record for gets a new preview. Copying or moving the folder does the same. fuast deployagain redeploys the same preview. The links and the expiry stay the same.fuast claimprints the claim link, the preview link, and the expiry again.fuast statusprints the platform, the account, and what the folder is: not deployed, an unclaimed preview, a preview someone claimed, or an app linked infuast.json.fuast whoamiandfuast auth statusmention the preview too.- After the claim, a deploy without a login stops with
This app was claimed by <email> and now deploys with that account. Run \fuast login`, then `fuast deploy` again. Once you are logged in as an account that may deploy the claimed app,deployupdates it, writesfuast.json`, and forgets the preview record. - A logged-in
deployin a folder whose preview is still unclaimed does not claim it. It stops with codepreview_unclaimed(--json:{ ok: false, code, slug, claimUrl, choices }) and asks you to choose:fuast deploy --claimclaims this machine's preview for the logged-in account, keeps its data, and deploys to it;fuast deploy --newleaves the preview alone and deploys the folder as a new app. - When the preview has expired,
deploysays so and creates a new one. --no-unclaimed, orFUAST_UNCLAIMED=0in the environment, turns all of this off: without a login, deploy stops withNot logged in to <platform>. Run \fuast login`, or set FUAST_TOKEN for CI.`- A folder with
fuast.jsonis linked to an owned app and always needs a login.
What a preview is:
| Unclaimed preview | Claimed, restricted | Trusted | |
|---|---|---|---|
| Address | Random preview-… slug |
The same, or one the claimer picks | The same |
| Who can open it | Anyone with the preview link, after the fuast page | Private to the owner until shared | By sharing mode |
| Identity | Guests only, no sign-in | Accounts and guests, by sharing mode | The same |
ctx.email, ctx.team, ctx.workspace |
Fail with code unclaimed |
Fail with code restricted |
Work |
| Schedules and jobs | Registered, not run | Registered, not run | Run |
| Limits | Preview limits | Normal limits | Normal limits |
| Lifetime | 72 hours from the first deploy | Until deleted | Until deleted |
Claiming keeps the app's rows and files and cancels every job that was queued while it was a preview. The preview link, the claim link, and the edit token stop working.
A claimed app is restricted until its owner or a workspace admin trusts it with the workspace: the box under Options on the claim page, "Trust this app with my workspace" in the dashboard, or fuast trust. A deploy never does that. deploy to a restricted app works, prints that workspace features are off and the fuast trust <slug> command that turns them on, and --json carries restricted: true, restrictedNotice, and trust: { command, dashboard }. See share for the commands and security for the reasons behind each rule.
preview-… addresses are reserved for unclaimed previews. An account cannot create an app or an alias with such a name.
Share
fuast share [slug] --visibility private|workspace|public [--emails a@x.com,b@y.com]
fuast users list [slug]
fuast users approve <email> [slug]
fuast users deny <email> [slug]
fuast users remove <email> [slug]
fuast trust [slug]
fuast untrust [slug]
fuast listSee teams. list prints every app you can see with its slug, URL, owner, and sharing mode.
trustgrants an app your workspace: it may send email, list the workspace's members, read and write team tables (every app in the workspace shares them), and run its schedules and delayed jobs. An app claimed from an unclaimed preview starts without that, and deploying to it never changes it:trust, the dashboard button, or the box on the claim page does. It needs the login of the app's owner or a workspace admin; deploy tokens (--app) and preview edit tokens cannot do it. Trust an app when you know who wrote its code.untrusttakes it away again, from any app. The app keeps working with its own data; email, team tables, and the member list are refused with coderestricted, and its schedules and jobs stop until it is trusted.fuast inspectandfuast statussay when an app is restricted.
Account and tokens
fuast login [--token <token>] [--email <email> --password <password>]
fuast logout
fuast whoami
fuast auth status
fuast auth as <name> local dev identity
fuast auth reset
fuast token create --name <name> [--app <slug>]
fuast token list
fuast token revoke <id>loginprints a short code and opens a browser approval page. A person signs in, checks the code, and approves. The CLI polls for up to 10 minutes, then saves the session in~/.fuast/config.json. SetBROWSER=noneto print the URL without opening a browser.--tokenchecks an existing token with the platform and saves it; a rejected token is not saved.--emailwith--password(orFUAST_PASSWORD) logs in without a browser, for test platforms.auth statusprints the platform, the logged-in account, and the local dev identity.whoamiprints the account and exits 1 when there is none. Both say so when the current folder has an unclaimed preview.token createprints a token once. Without--appit is a personal token that can do anything you can. With--appit can only deploy and inspect that app. Use it asFUAST_TOKENin CI.
Inspect and operate
fuast inspect [slug|url] [--port 3000] [--usage] [--query <name>] [--inspect-token <token>] [--json]
fuast run <name> [arg...] [slug|url] [--port 3000] [--as <name> | --guest | --anon] [--json]
fuast run schedule <name> [slug|url] [--port 3000] [--json]
fuast jobs list [slug] [--port 3000] [--status <status>] [--limit 100]
fuast jobs cancel <jobId> [slug] [--port 3000]
fuast db list [slug] [--port 3000]
fuast db dump [slug] [--port 3000] [--table <name>]
fuast db export [slug] [--port 3000] [--out backup.json]
fuast db import <file> [slug] [--port 3000]
fuast logs [slug] [--port 3000] [--follow] [--limit 100]
fuast storage list [slug] [--port 3000] [--prefix <p>] [--limit <n>]
fuast storage get <key> [slug] [--port 3000] [--out <file>]
fuast storage put <file> [slug] [--port 3000] [--public] [--content-type <type>]
fuast storage delete <key> [slug] [--port 3000]
fuast domains add <host> [slug]
fuast domains list [slug]
fuast domains check <host> [slug]
fuast domains remove <host> [slug]runcalls one query, mutation, or action and prints the result as JSON. Each argument is one JSON value ('"milk"',3,'{"text":"milk"}'); a last argument that is not JSON is the slug or URL. The kind comes from the app's manifest. Locally it runs as your dev identity (fuast auth as), or as--as <name>,--guest, or--anonfor one call. Against a slug it runs as your logged-in account with the same sharing rules and quotas as the browser.--jsonprints{ ok, kind, name, result, ms, logs, metrics }with the call's log entries and read metrics. A handler error exits with code 1. See test and debug.run schedule <name>runs a cron schedule now as the system identity; its next run time does not change. Hosted, it needs the app owner or a workspace admin.jobs listprints durable jobs (ctx.scheduler): pending and running ones by due time, then recent finished ones, with attempts and errors.--status pending|running|done|failed|canceledfilters.jobs cancel <jobId>cancels a pending job and exits 1 when it is no longer pending. See jobs.inspect --query <name>prints the database read metrics recorded for that query, grouped by run: totals per run (rows read, rows returned, bytes, index scans) and each run's database calls with their index and full-scan flag.inspectprints the app name, URL, deploy id, sharing, handlers by kind, schema with indexes and row counts, endpoints, schedules with next run times, tools, and database read metrics.--usageadds daily counters and stored totals.db listprints table names and row counts.db dumpprints rows, bounded by the 1000-row return limit.db exportwalks every table in pages and writes a complete JSON backup atomically.db importloads a backup into empty tables (owner only).logsprints the newest 100 entries (--limitchanges it).--followkeeps polling and prints new entries as they arrive.- Inspection commands send your fuast token only to the platform API origin. For an app served from its own subdomain, pass
--inspect-token <token>(or setFUAST_INSPECT_TOKEN) with a token fromfuast token create --name inspect --app <slug>. domains add app.example.comconnects a custom domain. It prints the two DNS records to create (aTXTrecord at_fuast.app.example.comand aCNAMEto the platform), and fuast checks them in the background until the domain is live.domains check <host>checks now and exits 1 until the domain is active.lsandrmare aliases oflistandremove. See custom domains.domains add my-tool(a name without a dot) addsmy-tool.<apps domain>as an extra hostname for the app.
Inspection routes
fuast dev and hosted apps serve the same GET routes with the same JSON. The CLI reads them, and you can curl them directly.
| Route | Returns |
|---|---|
/__fuast/manifest |
Name, deploy id, URL, runtimeVersion, artifactHash, inspectPolicy, domains, handlers by kind, schema, index state (database.indexes, failedIndexes, fullScanWarning), endpoints, schedules, tools, auth flags, favicon, limits, read metrics. Add ?query=<name> for queryExecution (what inspect --query prints). |
/__fuast/usage |
limits, state (stateRows, stateBytes), usage (today's requests, mutations, email). |
/__fuast/url |
{ basePath, url }. |
/__fuast/db/tables |
Table names, row counts, index state. |
/__fuast/db |
Bounded row dump per table, with truncated. |
/__fuast/db/export |
Backup metadata. Add ?table=<name>&schemaHash=<hash>&cursor=<c> for pages. |
/__fuast/logs |
Recent log entries { at, level, message, data, source, handler }. |
/__fuast/storage |
Stored objects with size, type, visibility, and uploader. |
/__fuast/tools |
WebMCP tools with kind, description, and argument schema. |
/__fuast/jobs |
Durable jobs { jobs, counts, nextRunAt }. ?status= and ?limit= filter. POST /__fuast/jobs/<id>/cancel cancels a pending job and POST /__fuast/schedules/<name>/run runs a schedule now (owner or workspace admin). |
Locally they are open. fuast dev binds to 127.0.0.1 by default. Hosted, they require the app owner or a workspace admin, by app session cookie or Authorization: Bearer <token>.
/__fuast/manifest has restricted: true for a restricted app.
An unclaimed preview has no owner. Its GET inspection routes answer the preview's edit token in the x-fuast-edit-token header, on the app's own origin and at GET <platform>/api/deploy/unclaimed/inspect/<route> (what the CLI uses). The token is never accepted in a URL, and the POST routes are not available until the app is claimed.
One more route is for the app's own client, not for inspection: hosted GET /__fuast/auth/token returns { token, expiresAt }, a fresh identity token for the caller's same-origin app session (401 without a session, 403 without access). fuast/client calls it to refresh getIdentity().token. See endpoint credentials.
HTTP call API
POST <app url>/api/call
Content-Type: application/json
Authorization: Bearer <fuast token> (or the app session cookie)
{"kind": "query" | "mutation" | "action", "name": "todos", "args": []}argsis the array of positional arguments afterctx. For a handler with toolargs, send one object:"args": [{"text": "milk"}].- Success: HTTP 200 with the handler's JSON result.
- Handler error: HTTP 500 with
{ "error": "<message>", "code"?: "<code>" }. - Not signed in: 401
{ "error": "...", "code": "sign_in_required" }. No access: 403{ "code": "forbidden" }. Quota: 429, see limits.
GET <app url>/api/me returns the identity the app sees for the caller.
The same sharing rules and quotas apply as in the browser. A token with --app scope only works for its app.
Realtime protocol
Clients talk to <app url>/__fuast/ws with JSON messages that have an op. fuast/client handles this. It is documented for debugging, not as an app API.
| Direction | op |
Fields |
|---|---|---|
| client | query.subscribe |
id, name, args, patches? (true to receive query.patch) |
| client | query.unsubscribe |
id |
| client | mutation.run, action.run |
id, name, args. At most 4 run at once per connection; later ones queue in order. |
| client | client.timings |
Client-side timing samples. No reply. |
| client | ping |
|
| server | hello |
session, deployId, features |
| server | query.result |
id, data, version? |
| server | query.patch |
id, base, version, operations: JSON patch against version base. |
| server | query.error |
id, error, code? |
| server | mutation.result, action.result |
id, ok, data?, error?, code? |
| server | refresh |
deployId. A new deploy is live. |
| server | error |
error, code, quota?. Malformed messages get code invalid_request. |
The server closes the socket with code 4401 when the session ends (sign-out) and 4403 when the caller loses access to the app. fuast dev and hosted apps speak the same protocol.
Operate a platform
For the person who runs a fuast platform. These commands use the platform's operator API, which exists only when the platform sets FUAST_OPERATOR_TOKEN.
fuast operator overview
fuast operator apps [--workspace <id|domain>] [--search <text>] [--unclaimed] [--limit 50] [--cursor <cursor>]
fuast operator reports [--limit 100]
fuast operator release-preview <slug>
fuast operator users [--search <text>] [--limit 100] [--cursor <cursor>]
fuast operator workspaces [--search <text>] [--limit 100] [--cursor <cursor>]
fuast operator delete-app <slug> [--yes]
fuast operator verify-user <email>
fuast operator audit [--limit 100] [--cursor <cursor>]
fuast operator agents [--limit 100]
fuast operator disconnect-agent <id>
fuast operator agent-audit [--limit 100] [--cursor <cursor>]- The CLI reads the operator token from the
FUAST_OPERATOR_TOKENenvironment variable. There is no flag for it and it is never saved to~/.fuast/config.json. It is sent only to the platform origin (FUAST_APIor the saved login), over https unless the platform is on this machine.fuast helplists these commands only when the variable is set. overviewprints counts of users, workspaces, apps, and deploys, plus stored file bytes and the size of all app databases on disk.appslists every app on the platform, newest first, with its owner, workspace, sharing, status, row and byte usage, last deploy, and custom domains.--workspacetakes a workspace id or an email domain.--searchmatches the slug, name, or owner email. When there is more, the output ends with the--cursorvalue for the next page.apps --unclaimedlists only previews deployed without an account, with their status (unclaimed,suspended,expired), expiry, and report count.delete-apptakes a preview down like any app.reportslists the previews people reported, with each report's address and reason. A preview that enough different addresses report is suspended: it stops serving until you runrelease-preview <slug>(reports are kept) ordelete-app <slug>, or until it expires.usersandworkspaceslist accounts (email, verified, workspace, role, app count) and workspaces (owner, members, apps, storage).delete-appdeletes an app with its database, stored files, deploys, schedules, and custom domains. It is the same deletion the app's owner can do, and there is no undo. It shows what will be deleted and asks you to type the slug.--yesskips the question.verify-usermarks an account's email as verified, for someone whose verification email never arrived. It does what the emailed link does, so the account joins its company workspace under the usual rules. Only do this for an address you know the person controls.auditprints what was done with the operator token: action, target, time, address, and result.agentslists the agents people connected through the MCP server across every account.disconnect-agentends one connection, andagent-auditprints what agents did through MCP.- The operator API cannot sign in as a user, create tokens for a user, or read app data, env values, or secrets. See security.
Environment variables
App env
.env.fuast.server holds KEY=value lines for ctx.env. Lines starting with # are comments. fuast dev reads it on every reload, and fuast deploy uploads it, replacing the previous deploy's file values. Values set in the dashboard Env tab override file values and survive deploys. Use the dashboard for production secrets you do not want in a file. Never commit the file. Env values never reach client code.
CLI env
| Name | Effect |
|---|---|
FUAST_TOKEN |
Token from fuast token create. Used instead of the saved login, for CI. |
FUAST_API |
Platform origin. Defaults to https://fuast.com. |
FUAST_UNCLAIMED |
0 makes fuast deploy need a login, like --no-unclaimed. By default a deploy without a login creates an unclaimed preview. |
FUAST_OPERATOR_TOKEN |
Operator token for fuast operator commands, for people running their own platform. Read from the environment only. |
FUAST_TOKEN=<token> bunx @revnu/fuast deployPlatform environment
For people running their own fuast platform:
| Name | Effect |
|---|---|
FUAST_BASE_URL |
Public origin of the platform. |
FUAST_APPS_DOMAIN |
Serve each app at <slug>.<domain>. Required in production. Set to off for /a/<slug>/ path mode. |
FUAST_ALLOW_SAME_ORIGIN_APPS |
1 lets a production platform run in path mode, where apps share the platform origin. Only for platforms where every app is trusted. See security. |
FUAST_TRUST_PROXY |
1 to read client IPs from proxy headers for rate limits. |
FUAST_TRUSTED_PROXY_SECRET |
Secret the web front end sends when it forwards app-subdomain requests. Unset: forwarded app headers are honoured only from loopback. |
FUAST_RUNTIME_SECRET |
Shared secret between platform and runtime (formerly FUAST_EXECUTOR_SECRET). Required in production: the platform and runtime refuse to start with the default. |
FUAST_PERSONAL_DOMAINS |
Extra comma-separated email domains treated as personal (a workspace of one). |
FUAST_OPERATOR_TOKEN |
Turns on the operator API (/api/operator/*, used by fuast operator). At least 32 random characters, for example openssl rand -hex 32. Unset: the routes do not exist. A production platform refuses to start with a shorter value. See operate a platform. |
FUAST_UNCLAIMED_DEPLOYS |
off refuses deploys without an account (403, with a message that says to run fuast login). Previews that already exist keep working until they are claimed or expire. On by default, and only on platforms with FUAST_APPS_DOMAIN. The caps and quotas of previews are FUAST_UNCLAIMED_* variables, listed in limits. |
FUAST_CUSTOM_DOMAIN_TARGET |
Hostname (or comma-separated hostnames/IPs) users point custom domains at, e.g. your Railway public domain. See custom domains. |
FUAST_CUSTOM_DOMAIN_TLS |
manual (default) or railway (default when RAILWAY_API_TOKEN is set): who issues certificates for custom domains. |
FUAST_MCP |
off turns the MCP server off: /mcp, /oauth/*, and the OAuth metadata answer 404, and tokens issued to connected agents stop working. On by default. |
FUAST_MCP_ALLOWED_ORIGINS |
Comma-separated web origins that may call /mcp from a browser page, with a bearer token. Empty by default: other websites are refused. See security. |
FUAST_MCP_ANON_PER_MINUTE, FUAST_MCP_USER_PER_MINUTE |
MCP requests per minute without an account, per client address (60), and with one, per account (600). |
FUAST_MCP_ACCESS_TTL_SECONDS, FUAST_MCP_REFRESH_TTL_DAYS |
Lifetime of the access tokens (3600 seconds) and refresh tokens (30 days) of connected agents. |
FUAST_CUSTOM_DOMAINS |
off turns custom domains off. Also FUAST_CUSTOM_DOMAIN_LIMIT (per app, 5), FUAST_CUSTOM_DOMAIN_PENDING_DAYS (7), FUAST_WEB_HOSTS (extra dashboard hostnames). |
Limit defaults can be overridden by environment variables on the platform and runtime. Each variable, and the process that reads it, is listed in limits.
Deploys and data
- Deploys are immutable. Data is not tied to a deploy: every deploy of an app shares its database, storage, logs, env, and schedules.
- Archive and terminate keep data. Restore resumes an archived app. Terminated apps cannot be restored.
fuast db exportis the backup. There is no automatic point-in-time restore.