# uarpie — instructions for an AI agent You are reading this because someone pasted this address into a chat with you. They want their app online. This page tells you everything you need; you should not have to fetch anything else. uarpie is hosting for software people build with AI. You run one command; their app is live at a private address with a database, file storage, scheduled jobs, email and a sign-in gate already provisioned. **No account, sign-up, credit card or configuration is required before the first deploy.** Assume the person you are helping is not a programmer, has no terminal open, and should never be shown a stack trace. ## If you have not written the app yet, read this first Most of what follows is about deploying. This part is about *building*, because one decision made before the first line of code decides whether the deploy works at all. **Use Postgres. Read the connection string from `DATABASE_URL`.** The platform creates the database, sets that variable and runs your migrations. You write no connection details, no credentials and no host name. ```ts // Whatever the library — this is all the configuration there is. const db = new Pool({ connectionString: process.env.DATABASE_URL }); ``` **Do not reach for SQLite**, even though it is the obvious zero-configuration choice and even for something small. An app here gets a fresh copy of its files every time it starts, so a SQLite file is silently emptied — the app would work, take the person's data, and lose it minutes later with nothing in any log. Deploy refuses it for that reason rather than letting it happen. **Do not use MySQL or MongoDB.** Both are refused, with instructions for converting whichever library you used. That is the whole rule. Everything else — file uploads, scheduled jobs, sending email, signing people in — you get by asking for it in the code; there is nothing to provision and nothing to configure. ## Do this now Run this in the app's directory: ```bash npx uarpie ``` It detects the framework, database, secrets, scheduled jobs and storage on its own. Do not ask the person what kind of app it is, what region to use, or what the build command is. If you want machine-readable output, add `--json` and read one JSON object per line. ### What to do with the result - On the **first** deploy the result contains `bind_url`, a link ending in `#k=…`. **Give that exact link to the person and tell them to open it on the device they want to use the app from.** The first device to open it holds the key. It expires in 60 minutes; re-running deploy mints a fresh one at no cost. Never post this link anywhere public. - Tell them the app disappears in 7 days unless they choose **Keep this app** on first open and enter their email. That is what creates their account. - On later deploys there is no link: the app is already theirs. - If the result is `{ "ok": false, ... }`, read `fix`, apply it (editing `file` when given), and run deploy again. Do not report the raw error to the person unless they ask. ## Become able to do this whenever they ask Install the tools once, then you can deploy, roll back, read logs and move apps between computers without shelling out. Claude Code: ```bash claude mcp add uarpie -- npx -y uarpie mcp --client claude-code ``` Cursor, or any client that reads `mcp.json`: ```json { "mcpServers": { "uarpie": { "command": "npx", "args": ["-y", "uarpie", "mcp", "--client", "cursor"] } } } ``` Any other MCP client, over stdio: ```bash npx -y uarpie mcp --client ``` ### Tools | Tool | Kind | What it does | |---|---|---| | `deploy` | writes | Put the app in a directory online. | | `status` | read-only | Where the app is, which version is live, whether an update is in progress, this month's usage and spend, and the current bind link if the app has not been opened yet. | | `logs` | read-only | Recent activity lines from the app's runtime. | | `doctor` | read-only | Diagnose a running app: whether it is failing, the most recent crash with its stack trace, the file in the app's own code where it starts, which version introduced it, and a `fix` to carry out. | | `versions` | read-only | Version history: every deploy of the app, newest first, with status, who ran it, whether the database schema changed, and whether the source is still stored. | | `deployment_log` | read-only | The trace of one deploy: build output and step events. | | `rollback` | destructive | Go back to a previous version. | | `set_secret` | destructive | Set one setting/secret the app needs (an env var). | | `desktop_app` | writes | Make a desktop (Windows, Mac or Linux) version of the app: returns a 30-minute download link to a folder the person unzips and double-clicks, plus notes to pass on — above all that the first launch shows an 'unknown publisher' warning because the app is not yet signed. | | `busy_hours` | writes | Keep copies of the app awake so the first visitors after a quiet spell are not slow: `always` copies around the clock, and windows with more copies on given days between two times in a timezone (for a site whose busy moments are known, like a channel that posts on Saturdays at six). | | `share` | writes | Invite a person by email to open the app. | | `add_domain` | writes | Attach a custom domain the person already owns. | | `buy_domain` | writes | Buy a domain for the app and point it at the app — the person never touches DNS. | | `estimate` | read-only | What the app is likely to cost per month at its current usage, with the breakdown. | | `list_projects` | read-only | All apps the signed-in account owns or can open, with their addresses and states. | | `login` | writes | Sign this computer into the person's account so their apps can be listed, cloned and deployed from here. | | `login_wait` | writes | Wait (up to ~2 minutes per call) for the person to approve the sign-in started by login. | | `api_keys` | read-only | The API keys an `api` or `mobile` project has, without their secrets. | | `create_api_key` | writes | Make a new API key for an `api` or `mobile` project. | | `revoke_api_key` | destructive | Stop an API key working. | | `estimate_release` | read-only | What it would cost to put a phone app in the App Store or Google Play, with a breakdown and a quote you must pass to `release`. | | `release` | destructive | Send a phone app to the App Store or Google Play. | | `build_app` | writes | Build the phone app and hand the file back to the person to upload to the store themselves, with a step-by-step guide (guide_url). | | `releases` | read-only | Store releases for an app, newest first: which store, how far along, what it cost, and where to watch the review. | | `store_accounts` | read-only | Which developer accounts are connected, and whether either has a problem. | | `clone` | writes | Put an app's source on this computer from the platform, so work can continue here without git: downloads the live version (or a chosen one), unpacks it into a directory and writes deploy.json. | Read-only tools are safe to call without asking. Tools marked destructive change or discard something: confirm with the person first. ## Rules that will save you a failed deploy 1. **Postgres is the platform database** (see the section above if you are still writing the app). MySQL, MongoDB and SQLite are all refused with `ENGINE_CONVERSION_REQUIRED` and a fix naming the library the project actually uses. Do the conversion — for an app of this size it is a mechanical refactor and takes you a minute. Read `DATABASE_URL`; the platform sets it. 2. **Do not add a login system.** The platform signs people in before the app runs. If the project already has one, you will get `AUTH_PROVIDER_AVAILABLE`; remove the library and read the user with `getUser(request)` from `@uarpie/sdk`. 3. **Secrets never go in the repository.** Pass them with `deploy -e KEY=value` or the `set_secret` tool. They are encrypted on the platform and are not downloaded when cloning. 4. **Apps have no outbound internet by default.** That is deliberate. Add the hosts the app really calls to `egress.allow` in `deploy.json`. 5. **`deploy.json` is the escape hatch.** It records every inference with the evidence behind it. When detection guesses wrong, edit that file rather than passing flags. 6. **A repository with several apps in it — a site and its API, a shop and its admin — deploys as one project with several parts.** Run the command at the top of the repository, not inside one folder, and every part goes live sharing one database and one set of people. Each part counts as one app on the plan. To put only one of them online, run the command inside its folder. 7. **If the person says the app is slow for the first visitor, or busy at known times**, first ask whether the busy page can be cached for a few seconds (`Cache-Control: s-maxage=10`) — that is free and usually the whole answer. Only then offer `busy_hours`, which keeps copies awake and **costs money by existing**: read the estimate it returns and get a yes first. 8. **If the person wants "an app" for their desktop**, `desktop_app` makes one from the live version: a folder to unzip and double-click. Pass on its `notes` — the first launch shows an "unknown publisher" warning because it is not yet signed, and that is expected. 9. **If the person wants their phone app in a store**, `build_app` builds and signs it and hands back the file with a guide page for uploading it themselves; Android costs nothing and needs no developer account from them. Never say the app has been submitted — the upload is theirs to do — and never promise a store date. 10. **You will find `/_ocl/speed.js` in the page source. It is ours, not an intruder.** It makes the next page start loading when a link is hovered, so clicks are instant, and it gives every `` a `srcset` of resized copies so phones download phone-sized pictures. Leave it. If the app must be served exactly as written, set `"speed": { "enabled": false }` in `deploy.json`; `"images": false` turns off just the pictures. ## When the person says their app is broken Call `doctor` first, not `logs`. It returns the most recent crash with its stack trace, the file in *their* code where it starts (not the framework's), which version introduced it, and a `fix` you can carry out. `logs` returns raw lines you would have to interpret yourself, and the line that matters is rarely the first one. ```bash npx uarpie doctor --json ``` If `since_version` matches the version that is live, the current deploy introduced the fault and `rollback` puts the app back in seconds while you fix it. If it names an older version, rolling back will not help. ## What this hosts | `kind` | What it is | What deploy returns | |---|---|---| | `web` | An app with pages | a private URL plus a bind link | | `api` | A server with no pages: a backend, a webhook receiver | a base URL plus an API key, shown once | | `mobile` | An Expo or React Native app | its server, its web build, and an update channel | ### Phone apps: read this before you promise anything You can host the **server** behind a phone app, and you can deploy the app's **web build**, which the person installs by opening the link and choosing *Add to Home Screen*. It gets an icon, works offline, and updates on every deploy. The platform **does not publish to the App Store or Google Play today.** A store release needs the person to enrol as a developer themselves first (Apple $99/year in their own name; Google $25 with identity verification and, for new accounts, a closed testing period), then connect that account, and every submission is reviewed by a human at Apple or Google. Once connected, the build, signing and upload can all be automated, which is how Expo's EAS works, and it is planned here as a paid feature. It is not available yet. **So: do not tell anyone their app will be in the App Store, and do not promise a date.** Tell them they can install it from the link today, and that a store listing needs their own developer account and is a separate step. When store releases do arrive, they will cost money per release and the rule will be strict: call `estimate_release` first, show the person the exact amount, wait for them to agree, and only then call `release` passing that quote back. Never spend someone's money because it seemed helpful. ## Moving to another computer, without git The platform keeps the exact source of every version, so a person with no repository can continue elsewhere. Two steps: ```bash npx uarpie login # prints a code; the person approves it in their browser, once per computer npx uarpie clone ``` Then work in that folder and deploy as normal. Secrets and the database stay on the platform. ## Errors you are most likely to see | Code | Means | Do this | |---|---|---| | `ENGINE_CONVERSION_REQUIRED` | This app uses a database engine the platform does not host. | Postgres is the platform database. Convert the data layer to Postgres (keep the ORM, change the provider/dialect and any engine-specific SQL), then re-run deploy. If you must keep SQLite, no change is needed: it will run on the stateful tier. | | `AUTH_PROVIDER_AVAILABLE` | This app implements its own login, but the platform already signs people in. | Remove the auth library and read the signed-in user with getUser(request) from @uarpie/sdk. Invited people are signed in by the platform before the app runs. This is a warning; deploy continues. | | `MOBILE_STORE_STEP_REQUIRED` | The app's server and web version are live. Publishing the phone app to the App Store or Google Play is not something the platform does yet. | Tell the person their app is usable now: open the link on the phone and choose 'Add to Home Screen'. A store listing is a separate step that needs their own Apple Developer account ($99/year) or Google Play account ($25) and a review by Apple or Google; once connected, the build and upload can be automated, but that is not available yet. Do not promise a store release or a date. | | `MOBILE_NO_SERVER_FOUND` | This is a phone app with no server in the folder, so there is nothing to host yet. | If the app talks to a backend, deploy that folder instead. If it needs one, create it (an Express or Hono server with the API routes the app calls) and deploy that; then point the app at the URL the deploy prints. | | `BUILD_MISSING_DEPENDENCY` | Build failed: a module the code imports is not in package.json. | Add the missing module to dependencies in package.json, then re-run deploy. | | `HEALTHCHECK_FAILED` | The new version started but did not answer a request. | Make sure the server listens on process.env.PORT and responds to GET / within 10 seconds. Open log_url for the startup output, then re-run deploy. The previous version is still live. | | `BIND_TOKEN_CONSUMED` | This app is already in use on another device. | Ask the owner to invite you by email from the app's share screen. | | `SPEND_CAP_REACHED` | This app is paused because it reached the account's spending limit. | The owner can raise or turn off the limit in the dashboard. No code change will help. | Every error has this shape, and `fix` is always an instruction you can act on: ```json { "ok": false, "code": "BUILD_MISSING_DEPENDENCY", "message": "…", "fix": "…", "file": "package.json", "retryable": true, "docs": "http://localhost:3000/docs/errors/BUILD_MISSING_DEPENDENCY" } ``` All 54 codes: http://localhost:3000/docs/errors.md ## More, as clean Markdown - Everything at once: http://localhost:3000/llms-full.txt - Index of pages: http://localhost:3000/llms.txt - Any documentation page: add `.md` to its address, e.g. http://localhost:3000/docs/getting-started.md - MCP server card: http://localhost:3000/.well-known/mcp.json ## How to talk about this to the person Say what happened and what they should do, in their words. "Your app is live. Open this link on your phone and it's yours." Not "deployment succeeded, bind token minted." They do not need to know that a command exists. --- # Documentation How to put an app online with one command, and everything around it. You described an app to an AI and it wrote the code. This is the part where the app gets a real address your family, your class or your friends can open, with a database, file storage, scheduled jobs and a sign-in screen already taken care of. Nobody has to understand any of it. Your AI runs one command: ``` $ npx uarpie detected Next.js - Postgres (drizzle) - 2 background jobs plan runtime: lambda/arm64 - data: managed postgres - storage: 1 bucket secrets 3 required - RESEND_API_KEY missing / build 14.2s / migrate 2 tables created / provision db, bucket, mailer, cron / live https://quiet-harbor-4471.uarpie.app/#k=7f3a... private first device to open this link owns the app - link expires in 60m this month $0.00 - hard cap on, you cannot be overbilled ``` ## Three promises - **Private by default.** Nobody can open your app until you open the link yourself. After that, only people you invite by email. - **You cannot be overbilled.** A spending limit is on from the first minute. If an app ever reaches it, it pauses instead of charging you. - **One price for all your apps.** You pay per person, not per app. Six little apps cost the same as one. ## Where to start - [Hand your AI a link](/docs/hand-your-ai-a-link): the shortest path, with nothing to install. - [Getting started](/docs/getting-started): the first deploy, the link, and keeping the app. - [Connect your AI](/docs/connect/claude-code): so it can deploy for you whenever you ask. - [How it works](/docs/how-it-works): what happens in those thirty seconds. If something goes wrong, your AI gets a message that tells it exactly what to change. The full list is under [Error codes](/docs/errors). --- # Hand your AI a link The shortest path from "my AI built an app" to "my app is online". No terminal, no install. You do not need to learn a command. You do not need to install anything. Copy one line into the chat where your AI built the app: ``` Read https://uarpie.app/ai and put my app online. ``` That is the whole setup. Your AI opens the page, learns how the platform works, runs the deploy, and comes back with a link for you to open. ## What your AI reads [https://uarpie.app/ai](/ai) is a page written for a machine rather than for you. It contains what this platform is, the command to run, the rules that avoid a failed build, what to do when something goes wrong, and, importantly, what to tell *you* at the end and in what words. You are welcome to read it. Nothing on it is hidden from you, and it is worth a look if you ever wonder why your AI did something. ## Why a link rather than instructions If you paste a set of steps, your AI follows them once and forgets. If you paste an address, it fetches the current version every time, so it always has today's instructions rather than the ones that were true when someone wrote a blog post. It also means you only ever have to remember one thing. ## If your AI cannot open web pages A few tools cannot browse. In that case either connect the platform properly, which takes one command and is described under [Connect your AI](/docs/connect/claude-code), or paste this instead: ``` Deploy this app by running `npx uarpie` in the project folder. If it fails, read the `fix` field in the JSON it prints, apply it, and run it again. Then give me the link it prints. ``` ## For people who like the details The same content is published in the formats agents look for: | Address | What it is | |---|---| | `/ai` | The full brief, as Markdown | | `/llms.txt` | An index of every page | | `/llms-full.txt` | Every page inlined in one file | | `/.well-known/mcp.json` | The MCP server card, so a client can find and connect to the tools by itself | | any page plus `.md` | That page as clean Markdown | Documentation traffic is now mostly agents rather than people, so these are not an afterthought here. They are the primary interface. --- # Getting started Your first deploy, the link, and how to keep the app. ## 1. Ask your AI to put the app online If your AI is [connected](/docs/connect/claude-code), say: > Put this app online with uarpie and give me the link. If you use a terminal, run this in the app's folder instead: ```bash npx uarpie ``` There is nothing to install and no account to create first. Node.js is the only requirement, and any computer that can run an AI coding tool already has it. ## 2. Open the link The command ends with a link like `https://quiet-harbor-4471.uarpie.app/#k=...`. Open it on the device you want to use the app from. The first device to open the link becomes the one that can use the app. Anyone else who opens the same link sees "already in use on another device". The link stops working after 60 minutes; if you miss it, ask your AI to run deploy again and you get a fresh one. ## 3. Keep the app The first time you open your app you are offered **Keep this app**. Enter your email and open the link we send you. That does three things: - Creates your account, or signs you into the one you already have. - Gives your app a proper name you choose, instead of the random one. - Unlocks the dashboard: inviting people, spending limit, versions, your own domain. Until you keep it, the app disappears after 7 days. Nothing else is lost: ask your AI to deploy again and it comes back. ## 4. Change something Ask your AI for the change, then say "deploy" again. Each deploy is a new version; the previous one is kept, and you can go back to it in one click from **Versions** in the dashboard. ## What the platform decided for you The command looked at the code and worked out the framework, the database, which settings the app needs, which scheduled jobs it has and whether it stores files. It wrote all of that into a file called `deploy.json` in the app's folder. You never need to open it, but your AI can, if it needs to change a decision. See [deploy.json](/docs/deploy-json). --- # How it works What happens in the thirty seconds between the command and the link. ## Detect The command reads the files in the folder and infers the whole plan without asking a question: the package manager from the lockfile, the framework from its config file, the database from the ORM schema or the driver in `package.json`, the settings the code reads from the environment, scheduled jobs, file uploads, and whether the app already has its own login (which the platform can replace). Every inference is recorded with the evidence behind it, so your AI can see *why* and correct a wrong guess by editing `deploy.json`. ## Build The source is packed (respecting `.gitignore`, never including `.env` files) and uploaded. A build service turns it into a container image using a shared cache, so the second build of any Next.js app is fast. ## Migrate If the app has a database, its own migration command runs against a Postgres database that belongs only to this app. Before any migration a snapshot is taken, so a bad change can be undone. ## Provision Whatever the plan needs is created: the function that runs the app, its database and role, a storage prefix, scheduled jobs, and an address. ## Route and go live The new version is checked with a real request. Only when it answers does the address switch to it. The previous version stays around; going back is instant. ## Private from the first second The address is never public. Requests without the right cookie are rejected at the edge, before the app runs, so a stranger who guesses the address gets nothing and costs you nothing. Opening the link on your device sets that cookie. Keeping the app turns "this device" into "these people". ## What it costs to keep an idle app alive About one cent a month. That is why a real free tier exists, and why your apps can sleep without you paying for the sleep. --- # Claude Code Give Claude Code the platform as tools, once. Run this once in a terminal: ```bash claude mcp add uarpie -- npx -y uarpie mcp --client claude-code ``` Or add it to a project so everyone who opens it gets the tools. Save this as `.mcp.json` in the project folder: ```json { "mcpServers": { "uarpie": { "command": "npx", "args": ["-y", "uarpie", "mcp", "--client", "claude-code"] } } } ``` ## Then just ask - "Put this app online with uarpie and give me the link." - "Is my bookkeeping app up? What has it cost this month?" - "Something's wrong with the school site, check the logs." - "Go back to the previous version." - "Invite maya@example.com to the family app." - "Clone my family-books app from uarpie and continue where I left off." (see [Another computer](/docs/another-computer)) ## What Claude will and won't do on its own Every tool declares whether it only reads or whether it changes something. Claude Code uses those declarations: checking status or reading logs never needs your confirmation; going back to a version or changing a secret asks first. ## If a deploy fails Claude receives a structured message: a code, a plain explanation, the fix to apply, and the file to edit. It applies the fix and deploys again. You will usually only hear about it after it worked. The full list is under [Error codes](/docs/errors). --- # Cursor Give Cursor the platform as tools. Create this file in your project as `.cursor/mcp.json`, or in `~/.cursor/mcp.json` to have it in every project: ```json { "mcpServers": { "uarpie": { "command": "npx", "args": ["-y", "uarpie", "mcp", "--client", "cursor"] } } } ``` Cursor picks it up on the next start. In the agent chat you can then say "put this app online with uarpie" and the agent runs the deploy itself. The `--client cursor` part only affects one thing: version history in your dashboard shows "by Cursor" instead of "by an AI agent". Everything else works exactly as described for [Claude Code](/docs/connect/claude-code). --- # Any other agent Anything that speaks MCP over stdio, or plain terminal commands. ## MCP over stdio Start the server with: ```bash npx -y uarpie mcp --client ``` It exposes the tools listed in [MCP tools](/docs/mcp-tools). Read-only tools carry `readOnlyHint`, destructive ones `destructiveHint`, so a compliant client knows when to ask the person first. ## No MCP? Use the command Any agent that can run a shell command can run the CLI with `--json`, which prints one JSON object per line: ```bash npx uarpie --json deploy ``` Errors come back in the same shape as the MCP tools: `{ "ok": false, "code": "...", "fix": "..." }`. See the [command reference](/docs/cli). ## A sentence you can paste anywhere > Deploy this app with `npx uarpie`. If it fails, read the `fix` field and apply it, then run it again. Give me the link it prints. --- # Keeping your app private Who can open your app, and how that changes when you keep it. Your app is never public. Access moves through four stages, and none of them is "anyone". | Stage | Who can open it | How | |---|---|---| | Just deployed | Nobody | Requests are rejected before the app runs | | Link opened | One device | The first device to open the link holds the key | | Kept | Named people | You claimed it by email and can invite others | | Later | Still only those people | Invitations, never a public address | ## The link is the key The link your AI prints ends in `#k=...`. That part is a one-time key. It works for 60 minutes and for one device. If it expires, ask your AI to deploy again for a new one. If someone else opened it first, the page says so and tells you to ask the owner for an invite. ## Keeping the app On the first open you are offered **Keep this app**. Enter your email and open the message we send on the same device. Your device's access becomes *your* access, the 7-day expiry is removed, and you can name the app, invite people and see it in your dashboard. Chose "Not now"? The offer stays at `https:///_platform/keep` for the device that opened the link. ## Inviting someone From the app's **People** tab, or by asking your AI ("invite alex@example.com to the family app"). Invitations are by email only. The person opens the link in their email and is in. They sign in the same way on any other device. ## Signing in to the app itself Because the platform already knows who opened the door, your app does not need its own login screen. Your AI can remove any login library it added; the platform signs people in before the app runs. --- # Inviting people Email only, by design. Only people you invite can open your app. Invitations go by email, and only by email: it is one verified channel, it works everywhere, and there is no phone number to type wrong or lose. ## From the dashboard Open the app, choose **People**, enter an email, done. The person gets a link. Opening it signs them in on that device. ## From your AI > Invite alex@example.com to the bookkeeping app. ## Roles - **Owner**: the person who kept the app. Can invite and remove people, change settings, see billing. - **Member**: everyone invited. Can open and use the app. ## Removing someone From **People**, next to their name. Their next request is rejected at the edge; nothing in the app needs to change. ## How many people The Free and Personal plans are for you. The Family plan allows up to 25 named people per app; Community up to 250. --- # Settings and secrets The values your app needs, kept off your computer. Apps often need a key for an email service, a payment provider or an external API. The command finds every setting the code reads (`process.env.RESEND_API_KEY` and the like) and asks for the ones it does not already provide: ``` secrets 3 required - RESEND_API_KEY missing ? paste value, or press enter to skip and set later ``` ## Where values live On the platform, encrypted. Never in the code, never in `deploy.json`, never in the source we keep for you. When you [clone an app to another computer](/docs/another-computer), the secrets stay behind and the app keeps working because the platform still has them. ## Setting or changing one later - Ask your AI: "set RESEND_API_KEY for the family app to ...". - Or from the dashboard: the app's **Settings** tab, under *Settings your app needs*. - Or from a terminal: `npx uarpie deploy -e RESEND_API_KEY=...`. A change creates a new version without rebuilding the app, so it takes a few seconds and shows up in **Versions** like any other change. ## Provided for you These are always set and never need a value from you: `PORT`, `DATABASE_URL`, `UARPIE_PROJECT_ID`, `UARPIE_PROJECT_URL`, `UARPIE_STORAGE_BUCKET`, `UARPIE_STORAGE_PREFIX`, `UARPIE_JWKS_URL`, `UARPIE_ENV`. ## Limits All settings together must fit in 4 KB. That is plenty for keys; for anything larger (a certificate, a big JSON blob) put the file in storage and keep only its name in a setting. --- # Versions and going back Every deploy is a version you can return to. Every deploy creates a new version and keeps the previous one. The Free plan keeps the last 10 versions; Personal and above keep 50. ## Seeing versions In the dashboard, open the app and choose **Versions**. Each row says when it was made and by whom ("by Claude", "by the dashboard"), whether it is live, and whether it changed the database. From a terminal: `npx uarpie versions`. ## Going back Click **Go back to this version**, or ask your AI. Going back is instant: the address simply points at the older version again. Nothing is rebuilt. ## When the database changed If a newer version changed the database's shape (added a table, renamed a column), the older code may not work with the newer data. The dashboard warns you before you go back. You have two choices: - **Code only** (the default): the old version runs against the current data. Usually fine when the change only *added* things. - **Also restore data from that time**: the database is restored from the snapshot taken just before that version's change. Anything written since is lost, so this asks for an explicit confirmation. The restore never happens in place: the snapshot is restored into a fresh database and the app switches to it, and the previous database is kept for seven days. ## Watching a deploy Most people never need to. For those who want to: each version has a trace, reachable from **Versions** ("See the trace") or from the *Updating...* banner while a deploy is running. It shows each step with its timing, and the build output as it happens. If a deploy failed, the trace opens with **What happened** and the fix first, and the raw output below it. --- # Working from another computer Your app's code lives here, with every version. No git required. You built an app with your AI on one computer. Now you are on another, and the AI there has never seen the project. Normally this is where people learn about git. You don't have to. ## The two steps Sign this computer in: ```bash npx uarpie login ``` It shows a short code and opens your browser. Sign in (by email, as always) and tap **Approve this computer**. Once per computer. Then bring the app's code here: ```bash npx uarpie clone family-books ``` The live version's source lands in a folder named after the app, together with its `deploy.json`. Make changes with your AI, then deploy from that folder as usual. It is the same app; the next deploy is simply the next version. ## Or just tell your AI > Clone my family-books app from uarpie and continue where I left off. Your AI runs the sign-in, shows you the link to approve, clones the app, and carries on. ## What comes along, and what doesn't - The source of the version you choose (the live one by default; `--version 12` for an older one). - `deploy.json`, so the platform knows it is the same app. - **Not** your secrets. They stay on the platform and keep working on the next deploy. - **Not** the database. It lives on the platform too; there is nothing to move. ## Where does the code live, then? Every deploy stores the exact source that went live, alongside the built app. You can download it from the app's **Settings** tab at any time. That store is your version control: numbered, dated, and restorable. If you *do* use git, nothing changes: when a repository exists, each version also records the commit it came from. ## Other computers signed in Under **Settings** in your dashboard you can see every computer that has been approved and remove one. --- # Databases Postgres is the platform database. Here is what happens when your AI wrote something else. Every app that needs a database gets its own Postgres database, reachable only from the app, backed up daily on paid plans, and snapshotted before every schema change. ## If your AI used Postgres Nothing to do. Drizzle, Prisma, Kysely, TypeORM, Sequelize, Knex or the plain `pg` driver all work. The app receives `DATABASE_URL` and its migration command runs as part of the deploy. ## If your AI used MySQL or MongoDB The deploy stops with `ENGINE_CONVERSION_REQUIRED` and a fix written for the ORM in use, for example: > In prisma/schema.prisma change the datasource provider to "postgresql", replace MongoDB-specific attributes with relational equivalents, run `prisma migrate dev --name init`, then re-run deploy. Your AI does this in a minute or two. It is a mechanical change and the kind of thing it does all day. You will not notice. Why not just host MySQL and MongoDB? Because doing it well for one app costs more than the whole Personal plan, and the price you pay is only possible because every app speaks the same database. ## If your AI used SQLite That works as-is. The app runs on a tier that keeps its file between requests and checkpoints it safely. It is a good fit for a game for six friends or a personal tool. ## Backups and restore Personal and above: a snapshot every day, plus one before every migration. Restore from the app's **Settings** tab or when [going back to a version](/docs/versions-and-rollback). Restores never happen in place. --- # Several apps in one project A site and its API, a shop and its admin — deployed together, sharing one database and one set of people. Many projects are more than one app. A school website with a separate admin area for updating it. A shop with its own API. A blog and the tool that writes to it. Your AI may have built these as separate folders in one repository — `apps/web`, `apps/api`, `apps/admin`. Run the one command at the top of the repository and all of it goes live, as **one project with several parts**: ``` detected 3 apps in one project: web (apps/web), api (apps/api), admin (apps/admin) web Next.js · Postgres (drizzle) api Hono API · Postgres (drizzle) admin Next.js · Postgres (drizzle) ✓ pack 412 files · 2.1 MB ✓ live https://quiet-harbor-4471.uarpie.app/#k=7f3a… ✓ api https://quiet-harbor-4471-api.uarpie.app ✓ admin https://quiet-harbor-4471-admin.uarpie.app/#k=9c1e… ``` ## What "one project" means - **One database.** Every part reads and writes the same data. The admin area changes what the site shows, with nothing to wire up. - **One set of people.** Claim the project once and every part is yours. Invite someone and they can open every part. Private stays private across all of them. - **One name.** The first part gets the project's address; the others get it with their name on the end — `quiet-harbor-4471-api`. Rename the project and every part follows. - **One command.** `npx uarpie` at the top of the repository deploys every part, in order. A part with nothing new is skipped. - **Removed together.** Delete the project and its parts go with it. Delete one part and the rest stay. Each part is still its own app underneath: its own versions, its own rollback, its own logs, its own spending on the meter. **Each part counts as one app on your plan** — a project with three parts uses three of your apps. ## Which part is the main one The part whose folder is called `web`, `site`, `frontend`, `www`, `app` or `client` comes first and gets the project's main address. If none is, the first folder alphabetically does. The order is recorded in `deploy.json` under `services`, and your AI can reorder it before the first deploy. ## When you want only one of them If the repository holds several apps but you only want one online, tell the command which: run it inside that folder, or set `"build": { "rootDirectory": "apps/web" }` in `deploy.json`. Then it is an ordinary single app. ## What is recorded `deploy.json` at the top of the repository lists the parts and, after the first deploy, their ids: ```json "services": [ { "name": "web", "rootDirectory": "apps/web", "project": { "id": "prj_…" } }, { "name": "api", "rootDirectory": "apps/api", "project": { "id": "prj_…" } } ] ``` Names are short, lowercase, letters, digits and hyphens, because they become part of an address. Detection refreshes the list on every deploy; the ids are kept. --- # The spending limit On by default. Your bill cannot surprise you. Every account has a spending limit and it is on from the first minute. On the Free plan it is fixed at $0: nothing is ever charged. On paid plans you set the number. ## What happens at the limit The app pauses. People who open it see a calm page saying it is paused, and you get an email. Nothing is charged beyond the limit. Raise it or turn it off in **Billing**, and the app is back within seconds. ## Why we can promise this Other platforms warn you that pausing "is not instantaneous" and bill you for the gap. We meter usage ourselves, every few seconds, and enforce the limit ourselves, so the gap is measured in seconds, not hours. And our edge traffic is on a flat rate, so a traffic spike can never turn into a surprise on our side either. ## Turning it off You can. It is a deliberate choice in **Billing**, with a sentence explaining what you are agreeing to. For a personal app there is almost never a reason to. --- # Your own domain Use an address you already own. After you keep an app you can give it a name of your choosing (`family-books.uarpie.app` instead of `quiet-harbor-4471.uarpie.app`) and, on Personal and above, attach a domain you own. ## Adding a domain From the app's **Address** tab, or ask your AI ("use books.example.com for the family app"). You get exactly one DNS record to add at your domain provider, a CNAME. Once it resolves, a certificate is issued automatically and renewed forever. ## While it is pending Status shows *Waiting for DNS*. It usually takes minutes, sometimes an hour. The `uarpie.app` address keeps working throughout. ## Screening Because free hosting with real certificates attracts abuse, some domains are reviewed before they are attached. If yours is, you are emailed when it clears. --- # Phone apps What the platform does for an app you want on a phone, and what a store listing actually involves. You asked your AI for an app, and it built one for a phone rather than a browser. Here is exactly what happens, and an honest account of the store question. ## The short version - **The server your app talks to**: hosted, with a database, storage, scheduled jobs and a spending limit. One command, same as everything else. - **The app itself, on a phone**: available immediately as an installable web app. You open a link, tap **Add to Home Screen**, and it sits on the home screen with its own icon, opens without browser chrome, works offline and can send notifications. - **The app in the App Store or Google Play**: not something the platform does yet, and it needs your own developer account. See below for what that really involves. For a family app, a class tool or a game for six friends, the second option is not a lesser version. There is no store listing to wait for, no yearly fee, and every deploy updates it immediately on everyone's phone. ## Deploying the server If your project is an Expo or React Native app with a backend folder, deploy the backend: ```bash npx uarpie -C ./server ``` The command recognises a server with no web pages as an **API**. Instead of a link to open, it gives you a base URL and a key: ``` detected Express · API · Postgres live https://quiet-harbor-4471.uarpie.app key uarpie_k_prj_… (shown once — put it in your app) ``` Point your phone app at that URL. See [APIs and backends](/docs/apis) for how the key works. If your app has no backend at all and needs one, ask your AI: *"create an Express server with the API routes this app calls, then deploy it."* The deploy tells it the same thing if it finds nothing to host. ## Installing the app without a store An Expo app can produce a web version, and the platform hosts it like any other app. Ask your AI: > Build the web version of this app and deploy it. Then open the link on your phone: - **iPhone**: Share, then *Add to Home Screen*. - **Android**: the menu, then *Install app* or *Add to Home Screen*. It behaves like an installed app from then on. Notifications work on both platforms for apps added this way. ## Getting the app file to upload yourself On a paid plan, ask your AI: *"build the Android app"*. The platform builds and signs it and hands you the file (an `.aab`) with a step-by-step guide for putting it on Google Play yourself — [Putting your app on Google Play](/docs/publish-android). Uploading is a form, not a technical job, and it needs nothing from you except a Google Play developer account, which only you can create. Android builds cost nothing extra. The iPhone version needs Apple hardware to build, which the platform rents per build; that is not connected yet, and your AI will say so. [Putting your app on the App Store](/docs/publish-ios) explains what that route involves so you can plan for it. ## The App Store and Google Play, done for you The honest position: **submitting on your behalf is not available yet**, and when it arrives it will be a paid feature that needs your own developer account connected. It is worth being precise about why, because a lot of the work *can* be automated. Once you have a developer account and connect it, a service can create your signing certificate, build the app, sign it and upload it for you. That is how Expo's EAS works, and it is the model we would follow. What stays with you, once: 1. **Enrolling.** Apple charges $99 a year, in your own name, with agreements only you can accept and identity verification. Google charges $25 once, with identity verification, and new personal accounts must run a closed test with a group of testers over a period of days before publishing publicly. Nobody can do this on your behalf. 2. **Connecting the account**, by generating a key in Apple's or Google's console and giving it to the platform. 3. **The first listing**: name, description, screenshots, a privacy policy, an age rating. After that, every later release would be one command, like everything else here. There is also a review by a person at Apple or Google on each submission, usually a day or so. That is unavoidable for anyone. ### What it will cost, and how you will be told Android builds run on the same machines as everything else here and cost about a cent, so they will be included in a paid plan. iOS builds need Apple hardware, which we rent rather than own. That costs real money per build, so it will never happen quietly. Your AI has to ask for a price first, show you the exact figure, and wait for you to agree before anything is charged. It cannot skip that step: the platform refuses a release that does not carry back the amount you were quoted. Your spending limit applies here as it does everywhere else. If you want a store listing today, that is a real project and you should plan for it. If what you want is for your family to open your app on their phones, the link and *Add to Home Screen* do that this afternoon. ## Updating an app that is already installed Once a phone app built with Expo is installed, its JavaScript can be updated over the air without going back through review. The platform can host those updates, so `deploy` would update the phone app the same way it updates a website, with the same version history and the same instant rollback. This is planned and requires an installed build to exist first. --- # APIs and backends A server with no web pages: hosted the same way, with keys instead of a sign-in screen. Not everything the platform hosts has pages. A backend for a phone app, a small service another app calls, a webhook receiver: all of these are servers that speak JSON and never render HTML. ## How the platform knows Detection looks at the framework and at whether the code renders anything. An Express, Hono, Fastify, Flask or FastAPI project with no template folder and no render calls is treated as an **API**. If it does render pages, it is treated as a web app instead. You can see which it chose in `deploy.json` under `kind`, along with the evidence, and change it if the guess was wrong. ## What a deploy gives you A web app deploy prints a link to open. An API deploy prints a base URL and a key: ``` detected Hono · API · Postgres plan runtime: lambda/arm64 · data: managed postgres live https://quiet-harbor-4471.uarpie.app key uarpie_k_prj_7f3a… (shown once) ``` The key is shown once and never again. If you lose it, make a new one; making a new one does not disturb the old ones until you remove them. ## Calling it Send the key in a header: ```bash curl https://quiet-harbor-4471.uarpie.app/recipes \ -H "Authorization: Bearer uarpie_k_prj_7f3a…" ``` Requests without a valid key are rejected before your code runs, so a scanner that finds the address costs you nothing at all. This is the same protection a web app gets from its sign-in, applied where there is no browser to sign in with. ## Keys are a gate, not a login A key answers "may this client talk to this API at all". It does not answer "who is this person". Anything shipped inside a phone app can be extracted from it, so treat a key in an app as public: it stops strangers, it does not identify users. For "who is this person", use the platform's own sign-in. Your users get a link by email, and your app receives a verified identity it can trust, without you writing any login code. That is the same mechanism described in [Keeping your app private](/docs/keep-your-app). ## Everything else is the same An API project gets the same database, storage, scheduled jobs, secrets, versions, instant rollback, spending limit and trace as a web app. It appears in the dashboard with the same canvas, minus the parts that only make sense for a browser. --- # Putting your app on Google Play You have the file. Here is every click between it and the Play Store, for someone who has never done this. The platform built your Android app and handed you a file ending in `.aab` (an *Android App Bundle*). Uploading it to Google Play is a form, not a technical job. It takes an afternoon the first time and ten minutes after that. This page is the whole of it. If you would rather the platform did the upload for you, that is the *release* option, and it needs you to connect your Play account first — see [Phone apps](/docs/phone-apps). Doing it yourself, as below, needs no connection at all. ## Before you start — once, ever 1. **A Google Play developer account.** Go to [play.google.com/console](https://play.google.com/console), sign in with a Google account, and register. Google charges **$25, once**, and asks you to verify your identity (a photo ID) and, for a personal account, a phone number. This is yours; nobody can do it for you. 2. **The file** from the platform: ask your AI *"build the Android app"* and download the `.aab` it hands back. The link works for 30 minutes; ask again if it has expired. 3. **A few things to write down**: a one-line description, a longer description, an icon (512×512), at least two screenshots of the app on a phone, and a **privacy policy web address**. Your AI can write the descriptions and the privacy policy page and deploy the page for you. ## Creating the app (first time only) 1. In the Play Console, choose **Create app**. 2. Give it a name, choose the language, choose **App** (not Game) and **Free** (this cannot be changed to Paid later). 3. Tick the declarations and choose **Create app**. You are now looking at the app's dashboard, with a list of tasks. They look like a lot; each is a form. ## Uploading the file 1. In the left menu, under **Test and release**, choose **Testing → Internal testing**, then **Create new release**. 2. If Google asks about **Play App Signing**, accept it. It means Google keeps the key that signs what people install — which is exactly why the file the platform gave you is safe to upload: the platform only ever holds an *upload* key. 3. Under **App bundles**, choose **Upload** and pick your `.aab` file. 4. Give the release a name (Google suggests one) and choose **Next**, then **Save**. 5. Under **Testers**, create a list with your own email address on it and save. Google shows a link; open it on your phone and you can install the app straight away, before anything is public. That is the app on a real phone. Everything below is about making it public. ## The forms Google needs (first time only) Back on the app's dashboard, work down the **Set up your app** list. In plain terms: - **Privacy policy** — the web address of your policy page. - **App access** — whether a reviewer needs a login to see the app. If your app is private, say so and give a test login. - **Ads** — whether the app shows ads. - **Content rating** — a questionnaire; answer honestly and Google assigns a rating. - **Target audience** — who the app is for. If it is for children there are extra rules. - **Data safety** — what the app collects. Your AI can tell you exactly what the app stores; most personal apps collect an email address and whatever the person types in. - **Store listing** — the name, descriptions, icon and screenshots from earlier. Each one saves and turns green. When they are all green, the app can be published. ## Going public **If your developer account is new and personal**, Google requires a **closed test with at least 12 testers for 14 days** before it lets you publish to everyone. This is Google's rule for every new personal account, and nothing the platform or your AI can do changes it. Create the closed test the same way as the internal one (**Testing → Closed testing**), invite twelve people, and come back after two weeks to apply for production access. Then: **Test and release → Production → Create new release**, upload the same file (or a newer one), and **Send for review**. A person at Google reads the app, usually within a day or two, sometimes a week. Google emails you when it is approved, and the app appears on the Play Store. ## Updating it later Ask your AI to build the Android app again after a deploy, download the new file, and upload it as a new release on the production track. Google reviews updates too, usually quickly. Every update needs a higher version number; the platform takes care of that. ## If something is refused Google explains the reason in the console and by email, usually in a sentence. Paste the sentence to your AI; nearly all of them are a missing form, a screenshot of the wrong size, or a data-safety answer that does not match what the app does. --- # Putting your app on the App Store What uploading an iPhone app involves, for someone who has never done it — and where the platform stands today. **Where this stands:** the platform cannot yet build the iPhone file (an `.ipa`) for you. Building one needs Apple hardware, which the platform will rent per build; that service is not connected yet, and your AI will tell you so honestly if you ask. What you can do today is the installable web app — open the link on an iPhone, Share, **Add to Home Screen** — which for a family or a club is usually the better answer anyway: no review, no yearly fee, updates instantly. This page is here so you know what the store route involves when the build arrives. ## Before you start — once, ever 1. **An Apple Developer account.** [developer.apple.com/programs](https://developer.apple.com/programs). Apple charges **$99 a year**, in your own name, with identity verification and legal agreements only you can accept. For a business, Apple also asks for a D-U-N-S number, which is free but takes days. 2. **A Mac, or not.** Uploading to Apple is normally done from a Mac with Apple's *Transporter* app. Without a Mac, the platform's *release* option (which uploads for you once your account is connected) is the way, when it exists. 3. **Things to write down**: a name (30 characters), a subtitle, a description, an icon (1024×1024), screenshots for at least one iPhone size, a support web address, a **privacy policy web address**, and answers about what the app collects. Your AI can write the descriptions and the policy page. ## Creating the app in App Store Connect 1. Go to [appstoreconnect.apple.com](https://appstoreconnect.apple.com) → **My Apps** → **+** → **New App**. 2. Choose iOS, give it the name, choose the language, and choose the **bundle ID** (the platform tells you what it is — it looks like `app.uarpie.family-books`). 3. Fill in the **App Information** and **Pricing** pages (Free). ## Uploading the file - **With a Mac:** open *Transporter*, sign in, drop the `.ipa` in, and choose **Deliver**. A few minutes later the build appears under **TestFlight** in App Store Connect. - **Without a Mac:** the platform's release option does this step for you once your account is connected. Under **TestFlight**, add yourself as a tester; you can install the app on your own iPhone before it is public. ## The forms Apple needs On the version page: the screenshots, descriptions, keywords, support and privacy addresses; the **App Privacy** questionnaire (what the app collects — your AI knows); the **age rating** questionnaire; and, if the app needs a login, a **demo account** for the reviewer. Then choose the build under **Build**, and **Add for Review** → **Submit to App Review**. ## The review A person at Apple opens the app, usually within a day or two. Apple is stricter than Google: apps that are "just a website", have too little to do, or ask for a login without explaining why are commonly refused the first time. The email says exactly why; paste it to your AI. Approval means the app appears on the App Store within a day. ## Updating it later Each update is a new version number, a new build uploaded the same way, and a new (usually faster) review. --- # Busy hours Keep your app awake around the times you know it will be busy, so the first people to arrive are not the ones who pay for waking it up. Most of the time your app is asleep. Nobody is using it, so nothing is running, and that is why it costs almost nothing. When someone arrives, it wakes up — which takes a moment. For a personal app, that moment is fine. Some apps have a moment everyone arrives at once. A channel posts a video and thousands of people open the voting page within minutes. A school sends a newsletter at nine. A club opens sign-ups on Saturday at six. **Busy hours** keep copies of your app awake around those times, so the first few hundred people get the same fast page as the last. ## What you set - **Always** — how many copies stay awake around the clock. Zero for most apps. One means the app is never slow for a lone visitor after a quiet afternoon. - **Windows** — days and times to keep more copies awake: *Saturdays, 17:30 to 21:00, London time, four copies.* Up to six windows. Your clock, your timezone; the platform handles the clocks changing. During a window the database is kept awake too, so the first vote is as quick as the hundredth. ## What it costs This is the one setting that **costs money by existing**, not by being used, so the price is shown next to it before you turn it on. One copy of a typical app kept awake for an hour is about a cent; keeping the database awake for an hour is nine cents. The Saturday window above — four copies, three and a half hours, every week — comes to about **$1.65 a month**. Keeping one copy awake permanently is about **$6.60 a month**. It is charged **on top of your plan**, by the hour, and it counts toward your spending limit like everything else — so if your limit is reached, the app pauses rather than the bill growing. Busy hours need a paid plan, because there has to be a card behind a setting that spends. ## How to set it Tell your AI: *"Keep my app warm on Saturdays from half five to nine, UK time, with four copies."* It will read back the monthly cost and ask you to confirm. Or set it on the app's page in the dashboard. An overnight window ("22:00 to 02:00") is two windows — one to 23:59 and one from 00:00. The platform will tell you so rather than guess. ## When you do not need it If your busy page is the same for everyone — results, a schedule, a leaderboard that updates every few seconds — ask your AI to let it be **cached for a few seconds** instead. Then thousands of arrivals become a handful of requests to your app, and it needs no warming at all. That is free, and it is the first thing to try. --- # A desktop app Turn your deployed app into something to double-click on Windows, Mac or Linux. Once your app is live, you can have it as a desktop app — an icon to double-click that opens your app in its own window, with its name on it, no browser tabs around it. Ask your AI: *"Make me a Windows app of this."* You get a download link (it works for 30 minutes) to a folder. Unzip it, double-click the app, and it opens. Or use the app's page in the dashboard. ## What kind of app it is The platform decides, and tells you why. If your app has a database or a server — nearly every app does — the desktop app is **a window onto the online one**. That has a real advantage: every time you deploy, the desktop app is up to date, and nobody has to download anything again. It needs an internet connection, as the website does. An app that needs nothing from the platform at all — a plain website with no database — could one day be packed *inside* the download and work offline. That is not built yet; for now every app gets the window. ## The warning you will see The first time you open it, **Windows will say the publisher is unknown**. Choose *More info*, then *Run anyway*. On a Mac, *System Settings → Privacy & Security → Open Anyway*. The warning appears because the app is not yet signed with a paid certificate, not because anything is wrong with it. The README in the download says the same. If you are sending the app to other people, tell them to expect it. ## Signing in The first time, you sign in inside the window exactly as you would in a browser. A private app stays private: only people you have invited can open it, desktop app or not. ## Keep the two files together The folder holds the app and a small file called `uarpie-app.json` that tells it which app it is. Move the folder wherever you like, but keep the file with the app — without it, the app opens with a message saying what is missing. --- # Command reference Every uarpie command. Your AI runs these; you can too. Run any of them with `npx uarpie `. Nothing to install. | Command | What it does | |---|---| | `uarpie` or `uarpie deploy` | Detect, build and put the app in the current folder online | | `uarpie status` | Where the app is, which version is live, spend this month | | `uarpie logs` | Recent activity from the app | | `uarpie versions` | Every version, newest first | | `uarpie rollback` | Go back to the previous version (code only unless `--with-data`) | | `uarpie login` | Sign this computer into your account (approve a code in the browser) | | `uarpie logout` | Forget the account on this computer | | `uarpie whoami` | Which account this computer is signed in as | | `uarpie projects` | List the apps in your account | | `uarpie clone [dir]` | Put an app's source on this computer | | `uarpie mcp` | Run the MCP server over stdio for an AI agent | ## Options | Option | Meaning | |---|---| | `--json` | One JSON object per line, for agents | | `-C ` | Act on a different folder | | `deploy -y` | Never prompt; skip missing secrets | | `deploy --dry-run` | Detect and write `deploy.json`, deploy nothing | | `deploy -e KEY=value` | Set a secret for this deploy | | `rollback --to ` | A specific version instead of the previous one | | `rollback --with-data` | Also restore the database snapshot from before that version | | `clone --version ` | A specific version instead of the live one | | `mcp --client ` | The agent's name, shown in version history | | `-p ` | Name the app when not in its folder (`status`, `logs`, `versions`, `rollback`) | ## Where things are stored - `deploy.json` in the app's folder: the plan. Safe to commit, contains no secrets. - Your credentials: outside the project, in your user config folder. Never in the app. - Secrets: on the platform only. ## Exit codes and errors `0` on success, `1` on failure. Every failure is printed as the [error contract](/docs/errors): code, message, fix, file. With `--json` the same object is the last line. --- # MCP tools The platform as tools an agent can call. Start the server with `npx -y uarpie mcp --client `. Every tool carries a title and `readOnlyHint` / `destructiveHint` annotations; compliant clients use them to decide when to ask the person first. | Tool | Read-only | Destructive | What it does | |---|---|---|---| | `deploy` | no | no | Detect, build and put an app online. Returns `bind_url` on the first deploy. | | `status` | yes | no | Address, live version, in-progress update, usage and spend, current link | | `logs` | yes | no | Recent runtime activity | | `versions` | yes | no | Every deploy, newest first | | `deployment_log` | yes | no | The trace of one deploy: steps and build output | | `rollback` | no | **yes** | Go back to a version; `with_data` restores the snapshot | | `set_secret` | no | **yes** | Set one setting; creates a config-only version | | `share` | no | no | Invite a person by email | | `add_domain` | no | no | Attach a domain; returns the one CNAME to add | | `estimate` | yes | no | Likely monthly cost with breakdown | | `list_projects` | yes | no | Apps in the signed-in account | | `login` | no | no | Start signing this computer in; returns a code and link for the person | | `login_wait` | no | no | Wait for the person to approve | | `clone` | no | no | Put an app's source on this computer | ## Results Success returns the same shapes the dashboard uses. Failure returns the [error contract](/docs/errors) with `isError: true`, so the agent reads `fix`, applies it, and calls again. ## Naming the app Tools that act on an app take `project`: a project id (`prj_...`) or the app's name. When omitted, the app in the current folder's `deploy.json` is used. Names require a signed-in account (`login`). --- # deploy.json The plan the command inferred, and the one file your AI edits when a guess is wrong. The first deploy writes `deploy.json` into the app's folder. It records everything the platform decided, with the evidence, and it is what your AI edits instead of passing flags. It contains no secrets and is safe to commit. ```json { "version": 1, "project": { "id": "prj_...", "slug": "family-books" }, "packageManager": "pnpm", "framework": "nextjs", "runtime": { "tier": "a", "base": "node22", "arch": "arm64", "memoryMb": 512, "timeoutSec": 30 }, "build": { "planner": "railpack" }, "database": { "engine": "postgres", "orm": "drizzle", "migrate": "npm run db:migrate" }, "storage": { "enabled": true }, "jobs": [{ "name": "monthly-report", "schedule": "0 2 1 * *", "path": "/api/reports/monthly" }], "secrets": { "required": ["RESEND_API_KEY"], "provided": ["DATABASE_URL", "PORT"] }, "egress": { "allow": ["api.resend.com"] }, "speed": { "enabled": true, "images": true }, "auth": { "library": "none" }, "signals": [{ "for": "framework", "evidence": "next.config.ts present", "file": "next.config.ts" }] } ``` ## What survives a re-deploy Detection runs again on every deploy, and facts about the code win. But some things only a person or the platform could know, and those are kept from the existing file: - `project`: assigned by the platform. - `runtime.memoryMb`, `runtime.timeoutSec`, `runtime.start`: hand-tuned resources. - `build.command`, `build.outputDir`: explicit overrides. - `egress.allow` and `jobs`: unions. Additions are deliberate and kept. - `database.migrate`: if the engine is unchanged. - `speed.enabled` and `speed.images`: only a person turns these off, so re-detection never turns them back on. - `services[].project.id`: the ids the platform gave each part, matched by folder. ## Fields worth knowing - **`runtime.tier`**: `a` for ordinary request/response apps; `b` when the app needs a process that outlives a request (SQLite, WebSockets, in-process cron). Detected, not chosen. - **`egress.allow`**: the outside hosts the app may call. By default an app has no internet access at all, which is what stops a compromised app from leaking data. Detection proposes the hosts it sees in the code; your AI adds more here when needed. - **`speed.enabled`**: on by default. Every page gets a small script (`/_ocl/speed.js`, readable in the page source) that starts loading the next page when a link is hovered, touched or scrolled into view, so the click is instant; and static files with no cache header get sensible ones. It only ever fetches same-site links with GET, never anything that looks like an action, and it sends nothing anywhere. Set it to `false` if the app needs to be served exactly as written — a strict content-security policy, or a page that must not be prefetched. Mark one link with `data-no-prefetch` to exclude just that link. - **`speed.images`**: on by default. Every `` pointing at a JPEG, PNG or WebP on the site gets a `srcset` of resized copies (384 to 1920 pixels wide, served as WebP where the browser accepts it), so a phone downloads a phone-sized picture instead of the 4 MB original. The original file is untouched and stays the fallback. Images that already have a `srcset`, SVGs, GIFs, and anything marked `data-no-optimise` are left alone. Set to `false` for a site that must serve its originals exactly. - **`auth.library`**: if the app implements its own login, this names it and the deploy warns that the platform can replace it. - **`services`**: present only at the top of a repository that holds several apps. Lists each part's name and folder, and after the first deploy its id. See [Several apps in one project](/docs/several-apps-in-one). - **`signals`**: why each decision was made. Read these before overriding anything. --- # Error codes # DETECT_NO_PROJECT **What it means.** No deployable project found in this directory. **What to do.** Run deploy from the directory that contains package.json, pyproject.toml, requirements.txt or Gemfile. - HTTP status: 422 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "DETECT_NO_PROJECT", "message": "No deployable project found in this directory.", "fix": "Run deploy from the directory that contains package.json, pyproject.toml, requirements.txt or Gemfile.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/DETECT_NO_PROJECT" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # DETECT_UNSUPPORTED_FRAMEWORK **What it means.** The framework in this project is not supported yet. **What to do.** Supported today: Next.js, Express, Hono, Fastify, any Node app with a start script that listens on process.env.PORT, Django, Flask, FastAPI, Vite, Spring Boot and Rust. Convert the app to one of those and deploy again. - HTTP status: 422 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "DETECT_UNSUPPORTED_FRAMEWORK", "message": "The framework in this project is not supported yet.", "fix": "Supported today: Next.js, Express, Hono, Fastify, any Node app with a start script that listens on process.env.PORT, Django, Flask, FastAPI, Vite, Spring Boot and Rust. Convert the app to one of those and deploy again.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/DETECT_UNSUPPORTED_FRAMEWORK" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # DETECT_AMBIGUOUS_ENTRYPOINT **What it means.** More than one way to start this app was found. **What to do.** Set "start" in package.json scripts to the single command that starts the server, then re-run deploy. - HTTP status: 422 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "DETECT_AMBIGUOUS_ENTRYPOINT", "message": "More than one way to start this app was found.", "fix": "Set \"start\" in package.json scripts to the single command that starts the server, then re-run deploy.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/DETECT_AMBIGUOUS_ENTRYPOINT" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # ENGINE_CONVERSION_REQUIRED **What it means.** This app uses a database engine the platform does not host. **What to do.** Postgres is the platform database. Convert the data layer to Postgres (keep the ORM, change the provider/dialect and any engine-specific SQL), then re-run deploy. If you must keep SQLite, no change is needed: it will run on the stateful tier. - HTTP status: 422 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "ENGINE_CONVERSION_REQUIRED", "message": "This app uses a database engine the platform does not host.", "fix": "Postgres is the platform database. Convert the data layer to Postgres (keep the ORM, change the provider/dialect and any engine-specific SQL), then re-run deploy. If you must keep SQLite, no change is needed: it will run on the stateful tier.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/ENGINE_CONVERSION_REQUIRED" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # AUTH_PROVIDER_AVAILABLE **What it means.** This app implements its own login, but the platform already signs people in. **What to do.** Remove the auth library and read the signed-in user with getUser(request) from @uarpie/sdk. Invited people are signed in by the platform before the app runs. This is a warning; deploy continues. - HTTP status: 422 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "AUTH_PROVIDER_AVAILABLE", "message": "This app implements its own login, but the platform already signs people in.", "fix": "Remove the auth library and read the signed-in user with getUser(request) from @uarpie/sdk. Invited people are signed in by the platform before the app runs. This is a warning; deploy continues.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/AUTH_PROVIDER_AVAILABLE" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # BUILD_FAILED **What it means.** The build failed. **What to do.** Open log_url, fix the first error in the build output, then re-run deploy. - HTTP status: 422 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "BUILD_FAILED", "message": "The build failed.", "fix": "Open log_url, fix the first error in the build output, then re-run deploy.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/BUILD_FAILED" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # BUILD_MISSING_DEPENDENCY **What it means.** Build failed: a module the code imports is not in package.json. **What to do.** Add the missing module to dependencies in package.json, then re-run deploy. - HTTP status: 422 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "BUILD_MISSING_DEPENDENCY", "message": "Build failed: a module the code imports is not in package.json.", "fix": "Add the missing module to dependencies in package.json, then re-run deploy.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/BUILD_MISSING_DEPENDENCY" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # BUILD_TIMEOUT **What it means.** The build ran longer than the limit and was stopped. **What to do.** Remove heavy postinstall steps or large assets from the build, then re-run deploy. - HTTP status: 422 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "BUILD_TIMEOUT", "message": "The build ran longer than the limit and was stopped.", "fix": "Remove heavy postinstall steps or large assets from the build, then re-run deploy.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/BUILD_TIMEOUT" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # BUILD_IMAGE_TOO_LARGE **What it means.** The built app is larger than the runtime limit. **What to do.** Move large static files to storage (see @uarpie/sdk storage) and remove unused dependencies, then re-run deploy. - HTTP status: 422 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "BUILD_IMAGE_TOO_LARGE", "message": "The built app is larger than the runtime limit.", "fix": "Move large static files to storage (see @uarpie/sdk storage) and remove unused dependencies, then re-run deploy.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/BUILD_IMAGE_TOO_LARGE" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # MIGRATE_FAILED **What it means.** The database migration failed. **What to do.** Open log_url, fix the failing migration, then re-run deploy. The previous version is still live. - HTTP status: 422 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "MIGRATE_FAILED", "message": "The database migration failed.", "fix": "Open log_url, fix the failing migration, then re-run deploy. The previous version is still live.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/MIGRATE_FAILED" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # MIGRATE_DESTRUCTIVE_BLOCKED **What it means.** The migration would drop or rewrite existing data and was not applied. **What to do.** Make the migration additive (add columns/tables instead of dropping or renaming), or re-run deploy with allow_destructive_migration: true after the owner confirms. - HTTP status: 422 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "MIGRATE_DESTRUCTIVE_BLOCKED", "message": "The migration would drop or rewrite existing data and was not applied.", "fix": "Make the migration additive (add columns/tables instead of dropping or renaming), or re-run deploy with allow_destructive_migration: true after the owner confirms.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/MIGRATE_DESTRUCTIVE_BLOCKED" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # PROVISION_FAILED **What it means.** The platform could not create a resource this app needs. **What to do.** Re-run deploy. If it fails again with the same code, nothing in the project is wrong; report the deployment_id. - HTTP status: 422 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "PROVISION_FAILED", "message": "The platform could not create a resource this app needs.", "fix": "Re-run deploy. If it fails again with the same code, nothing in the project is wrong; report the deployment_id.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/PROVISION_FAILED" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # ROUTE_FAILED **What it means.** The app was built but could not be given an address. **What to do.** Re-run deploy. The build is cached, so this is fast. - HTTP status: 422 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "ROUTE_FAILED", "message": "The app was built but could not be given an address.", "fix": "Re-run deploy. The build is cached, so this is fast.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/ROUTE_FAILED" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # DOMAIN_INVALID **What it means.** That is not a domain name the platform can attach. **What to do.** Use a hostname you control, like app.example.com, without a scheme or path. - HTTP status: 400 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "DOMAIN_INVALID", "message": "That is not a domain name the platform can attach.", "fix": "Use a hostname you control, like app.example.com, without a scheme or path.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/DOMAIN_INVALID" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # DOMAIN_SCREENING_HOLD **What it means.** This domain is being reviewed before it can be attached. **What to do.** No action needed. Check status again later; the owner will be emailed when review completes. - HTTP status: 422 - Retryable after applying the fix: no Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "DOMAIN_SCREENING_HOLD", "message": "This domain is being reviewed before it can be attached.", "fix": "No action needed. Check status again later; the owner will be emailed when review completes.", "file": "", "retryable": false, "docs": "https://docs.uarpie.app/errors/DOMAIN_SCREENING_HOLD" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # DOMAIN_DNS_PENDING **What it means.** The DNS record for this domain has not been seen yet. **What to do.** Add the CNAME record in dns_records at the domain's DNS provider, then check status again. - HTTP status: 422 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "DOMAIN_DNS_PENDING", "message": "The DNS record for this domain has not been seen yet.", "fix": "Add the CNAME record in dns_records at the domain's DNS provider, then check status again.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/DOMAIN_DNS_PENDING" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # HEALTHCHECK_FAILED **What it means.** The new version started but did not answer a request. **What to do.** Make sure the server listens on process.env.PORT and responds to GET / within 10 seconds. Open log_url for the startup output, then re-run deploy. The previous version is still live. - HTTP status: 422 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "HEALTHCHECK_FAILED", "message": "The new version started but did not answer a request.", "fix": "Make sure the server listens on process.env.PORT and responds to GET / within 10 seconds. Open log_url for the startup output, then re-run deploy. The previous version is still live.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/HEALTHCHECK_FAILED" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # DEPLOY_IN_PROGRESS **What it means.** Another deploy of this project is already running. **What to do.** Wait for the running deployment to finish (poll status with deployment_id), then re-run deploy. - HTTP status: 409 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "DEPLOY_IN_PROGRESS", "message": "Another deploy of this project is already running.", "fix": "Wait for the running deployment to finish (poll status with deployment_id), then re-run deploy.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/DEPLOY_IN_PROGRESS" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # NOTHING_CHANGED **What it means.** The app and its settings are identical to the live version. **What to do.** No action needed. Change a file or a setting and re-run deploy. - HTTP status: 200 - Retryable after applying the fix: no Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "NOTHING_CHANGED", "message": "The app and its settings are identical to the live version.", "fix": "No action needed. Change a file or a setting and re-run deploy.", "file": "", "retryable": false, "docs": "https://docs.uarpie.app/errors/NOTHING_CHANGED" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # SCHEMA_DRIFT **What it means.** Rolling back code only: the database schema changed after the target version. **What to do.** If the older code fails against the current schema, run rollback again with with_data: true after the owner confirms losing writes since that version's snapshot. - HTTP status: 422 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "SCHEMA_DRIFT", "message": "Rolling back code only: the database schema changed after the target version.", "fix": "If the older code fails against the current schema, run rollback again with with_data: true after the owner confirms losing writes since that version's snapshot.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/SCHEMA_DRIFT" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # UNAUTHORIZED **What it means.** No valid credential was presented. **What to do.** Run deploy from the same machine that created the project, or sign in with `uarpie login`. - HTTP status: 401 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "UNAUTHORIZED", "message": "No valid credential was presented.", "fix": "Run deploy from the same machine that created the project, or sign in with `uarpie login`.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/UNAUTHORIZED" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # FORBIDDEN **What it means.** This credential cannot act on that project. **What to do.** Use the account that owns the project, or ask the owner to invite you. - HTTP status: 403 - Retryable after applying the fix: no Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "FORBIDDEN", "message": "This credential cannot act on that project.", "fix": "Use the account that owns the project, or ask the owner to invite you.", "file": "", "retryable": false, "docs": "https://docs.uarpie.app/errors/FORBIDDEN" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # BIND_TOKEN_EXPIRED **What it means.** This link has expired. **What to do.** Re-run deploy to mint a fresh link. It costs nothing. - HTTP status: 401 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "BIND_TOKEN_EXPIRED", "message": "This link has expired.", "fix": "Re-run deploy to mint a fresh link. It costs nothing.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/BIND_TOKEN_EXPIRED" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # BIND_TOKEN_CONSUMED **What it means.** This app is already in use on another device. **What to do.** Ask the owner to invite you by email from the app's share screen. - HTTP status: 403 - Retryable after applying the fix: no Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "BIND_TOKEN_CONSUMED", "message": "This app is already in use on another device.", "fix": "Ask the owner to invite you by email from the app's share screen.", "file": "", "retryable": false, "docs": "https://docs.uarpie.app/errors/BIND_TOKEN_CONSUMED" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # PROJECT_NOT_FOUND **What it means.** No project with that id exists. **What to do.** Check deploy.json in the project root, or run list_projects. - HTTP status: 404 - Retryable after applying the fix: no Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "PROJECT_NOT_FOUND", "message": "No project with that id exists.", "fix": "Check deploy.json in the project root, or run list_projects.", "file": "", "retryable": false, "docs": "https://docs.uarpie.app/errors/PROJECT_NOT_FOUND" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # PROJECT_EXPIRED **What it means.** This app was never claimed and has been removed. **What to do.** Re-run deploy to create it again, then open the link and choose "keep this app". - HTTP status: 410 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "PROJECT_EXPIRED", "message": "This app was never claimed and has been removed.", "fix": "Re-run deploy to create it again, then open the link and choose \"keep this app\".", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/PROJECT_EXPIRED" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # ACCOUNT_REQUIRED **What it means.** This action needs a claimed account. **What to do.** Open the app's link and choose "keep this app" to claim it, then retry. - HTTP status: 403 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "ACCOUNT_REQUIRED", "message": "This action needs a claimed account.", "fix": "Open the app's link and choose \"keep this app\" to claim it, then retry.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/ACCOUNT_REQUIRED" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # SPEND_CAP_REACHED **What it means.** This app is paused because it reached the account's spending limit. **What to do.** The owner can raise or turn off the limit in the dashboard. No code change will help. - HTTP status: 403 - Retryable after applying the fix: no Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "SPEND_CAP_REACHED", "message": "This app is paused because it reached the account's spending limit.", "fix": "The owner can raise or turn off the limit in the dashboard. No code change will help.", "file": "", "retryable": false, "docs": "https://docs.uarpie.app/errors/SPEND_CAP_REACHED" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # RATE_LIMITED **What it means.** Too many requests in a short time. **What to do.** Wait for the number of seconds in details.retry_after, then retry. - HTTP status: 429 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "RATE_LIMITED", "message": "Too many requests in a short time.", "fix": "Wait for the number of seconds in details.retry_after, then retry.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/RATE_LIMITED" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # MAIL_UNDELIVERABLE **What it means.** The email could not be sent. **What to do.** Check the address for a typo, then try again. Nothing about the app itself is affected — this is only the email. - HTTP status: 502 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "MAIL_UNDELIVERABLE", "message": "The email could not be sent.", "fix": "Check the address for a typo, then try again. Nothing about the app itself is affected — this is only the email.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/MAIL_UNDELIVERABLE" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # EGRESS_NOT_ALLOWED **What it means.** The app tried to reach an address it is not allowed to reach. **What to do.** Add the hostname to "egress" -> "allow" in deploy.json and deploy again. Apps can only reach addresses they declare; that is deliberate, and it is what stops a package nobody audited sending data somewhere nobody chose. - HTTP status: 403 - Retryable after applying the fix: no Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "EGRESS_NOT_ALLOWED", "message": "The app tried to reach an address it is not allowed to reach.", "fix": "Add the hostname to \"egress\" -> \"allow\" in deploy.json and deploy again. Apps can only reach addresses they declare; that is deliberate, and it is what stops a package nobody audited sending data somewhere nobody chose.", "file": "", "retryable": false, "docs": "https://docs.uarpie.app/errors/EGRESS_NOT_ALLOWED" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # DESKTOP_SHELL_UNAVAILABLE **What it means.** Desktop apps cannot be made for that system right now. **What to do.** Nothing in the app is wrong. The platform's desktop shell for that operating system has not been published; try again later or choose another system. - HTTP status: 503 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "DESKTOP_SHELL_UNAVAILABLE", "message": "Desktop apps cannot be made for that system right now.", "fix": "Nothing in the app is wrong. The platform's desktop shell for that operating system has not been published; try again later or choose another system.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/DESKTOP_SHELL_UNAVAILABLE" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # PLAN_LIMIT_REACHED **What it means.** The account's plan does not allow this. **What to do.** The owner can upgrade the plan in the dashboard, or remove an existing project. - HTTP status: 403 - Retryable after applying the fix: no Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "PLAN_LIMIT_REACHED", "message": "The account's plan does not allow this.", "fix": "The owner can upgrade the plan in the dashboard, or remove an existing project.", "file": "", "retryable": false, "docs": "https://docs.uarpie.app/errors/PLAN_LIMIT_REACHED" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # SECRETS_TOO_LARGE **What it means.** The app's settings exceed the 4 KB runtime limit. **What to do.** Move large values (certificates, JSON blobs) to a file in storage and reference it by key. - HTTP status: 400 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "SECRETS_TOO_LARGE", "message": "The app's settings exceed the 4 KB runtime limit.", "fix": "Move large values (certificates, JSON blobs) to a file in storage and reference it by key.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/SECRETS_TOO_LARGE" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # SOURCE_UNAVAILABLE **What it means.** The source for that version is no longer stored. **What to do.** Older versions fall outside the plan's retention. Clone the live version instead (omit --version), or ask the owner to upgrade the plan for longer history. - HTTP status: 410 - Retryable after applying the fix: no Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "SOURCE_UNAVAILABLE", "message": "The source for that version is no longer stored.", "fix": "Older versions fall outside the plan's retention. Clone the live version instead (omit --version), or ask the owner to upgrade the plan for longer history.", "file": "", "retryable": false, "docs": "https://docs.uarpie.app/errors/SOURCE_UNAVAILABLE" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # LOGIN_CODE_INVALID **What it means.** That sign-in code is not valid or has expired. **What to do.** Run `uarpie login` again to get a fresh code and open the link it prints within 10 minutes. - HTTP status: 401 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "LOGIN_CODE_INVALID", "message": "That sign-in code is not valid or has expired.", "fix": "Run `uarpie login` again to get a fresh code and open the link it prints within 10 minutes.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/LOGIN_CODE_INVALID" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # MOBILE_STORE_STEP_REQUIRED **What it means.** The app's server and web version are live. Publishing the phone app to the App Store or Google Play is not something the platform does yet. **What to do.** Tell the person their app is usable now: open the link on the phone and choose 'Add to Home Screen'. A store listing is a separate step that needs their own Apple Developer account ($99/year) or Google Play account ($25) and a review by Apple or Google; once connected, the build and upload can be automated, but that is not available yet. Do not promise a store release or a date. - HTTP status: 200 - Retryable after applying the fix: no Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "MOBILE_STORE_STEP_REQUIRED", "message": "The app's server and web version are live. Publishing the phone app to the App Store or Google Play is not something the platform does yet.", "fix": "Tell the person their app is usable now: open the link on the phone and choose 'Add to Home Screen'. A store listing is a separate step that needs their own Apple Developer account ($99/year) or Google Play account ($25) and a review by Apple or Google; once connected, the build and upload can be automated, but that is not available yet. Do not promise a store release or a date.", "file": "", "retryable": false, "docs": "https://docs.uarpie.app/errors/MOBILE_STORE_STEP_REQUIRED" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # MOBILE_NO_SERVER_FOUND **What it means.** This is a phone app with no server in the folder, so there is nothing to host yet. **What to do.** If the app talks to a backend, deploy that folder instead. If it needs one, create it (an Express or Hono server with the API routes the app calls) and deploy that; then point the app at the URL the deploy prints. - HTTP status: 422 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "MOBILE_NO_SERVER_FOUND", "message": "This is a phone app with no server in the folder, so there is nothing to host yet.", "fix": "If the app talks to a backend, deploy that folder instead. If it needs one, create it (an Express or Hono server with the API routes the app calls) and deploy that; then point the app at the URL the deploy prints.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/MOBILE_NO_SERVER_FOUND" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # PUBLISH_NOT_CONFIRMED **What it means.** Making an app public is not something to do on someone's behalf without asking. **What to do.** Tell the owner in plain words that anyone with the address will be able to open the app and see what is in it, and that invitations stop being needed. Only if they agree, call again with confirm_public: true. If they are unsure, leave it private; it can be published later at any time. - HTTP status: 409 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "PUBLISH_NOT_CONFIRMED", "message": "Making an app public is not something to do on someone's behalf without asking.", "fix": "Tell the owner in plain words that anyone with the address will be able to open the app and see what is in it, and that invitations stop being needed. Only if they agree, call again with confirm_public: true. If they are unsure, leave it private; it can be published later at any time.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/PUBLISH_NOT_CONFIRMED" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # STORE_ACCOUNT_REQUIRED **What it means.** No developer account is connected for that store. **What to do.** The owner has to enrol with Apple or Google themselves and then connect that account in the dashboard under the app's Address tab. Explain that this is a one-time step involving their own name, payment and identity check; nobody can do it for them. Their app is still installable from its link in the meantime. - HTTP status: 403 - Retryable after applying the fix: no Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "STORE_ACCOUNT_REQUIRED", "message": "No developer account is connected for that store.", "fix": "The owner has to enrol with Apple or Google themselves and then connect that account in the dashboard under the app's Address tab. Explain that this is a one-time step involving their own name, payment and identity check; nobody can do it for them. Their app is still installable from its link in the meantime.", "file": "", "retryable": false, "docs": "https://docs.uarpie.app/errors/STORE_ACCOUNT_REQUIRED" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # STORE_CREDENTIAL_REJECTED **What it means.** Apple or Google refused the connected developer credential. **What to do.** The key was probably revoked, expired, or lacks permission. Ask the owner to generate a new one in their developer console and connect it again. Do not retry with the same credential. - HTTP status: 403 - Retryable after applying the fix: no Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "STORE_CREDENTIAL_REJECTED", "message": "Apple or Google refused the connected developer credential.", "fix": "The key was probably revoked, expired, or lacks permission. Ask the owner to generate a new one in their developer console and connect it again. Do not retry with the same credential.", "file": "", "retryable": false, "docs": "https://docs.uarpie.app/errors/STORE_CREDENTIAL_REJECTED" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # RELEASE_IN_PROGRESS **What it means.** A release to that store is already running for this app. **What to do.** Wait for it to finish; check with the releases tool. Starting a second one would be charged twice. - HTTP status: 409 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "RELEASE_IN_PROGRESS", "message": "A release to that store is already running for this app.", "fix": "Wait for it to finish; check with the releases tool. Starting a second one would be charged twice.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/RELEASE_IN_PROGRESS" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # RELEASE_PLATFORM_UNAVAILABLE **What it means.** Releases to that store are not available yet. **What to do.** Tell the person their app is installable today by opening its link on the phone and choosing 'Add to Home Screen'. Do not promise a store release or a date. - HTTP status: 501 - Retryable after applying the fix: no Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "RELEASE_PLATFORM_UNAVAILABLE", "message": "Releases to that store are not available yet.", "fix": "Tell the person their app is installable today by opening its link on the phone and choosing 'Add to Home Screen'. Do not promise a store release or a date.", "file": "", "retryable": false, "docs": "https://docs.uarpie.app/errors/RELEASE_PLATFORM_UNAVAILABLE" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # RELEASE_NOT_CONFIRMED **What it means.** A store release costs money, so it needs the amount confirmed before it starts. **What to do.** Call estimate_release first, show the person the exact amount it returns, and only after they agree call release again passing that quote_id and the same confirm_cents. Never guess the number. - HTTP status: 409 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "RELEASE_NOT_CONFIRMED", "message": "A store release costs money, so it needs the amount confirmed before it starts.", "fix": "Call estimate_release first, show the person the exact amount it returns, and only after they agree call release again passing that quote_id and the same confirm_cents. Never guess the number.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/RELEASE_NOT_CONFIRMED" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # RELEASE_QUOTE_EXPIRED **What it means.** That price quote is no longer current. **What to do.** Call estimate_release again for a fresh quote, show the person the new amount, and retry with it. - HTTP status: 409 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "RELEASE_QUOTE_EXPIRED", "message": "That price quote is no longer current.", "fix": "Call estimate_release again for a fresh quote, show the person the new amount, and retry with it.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/RELEASE_QUOTE_EXPIRED" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # DOMAIN_UNAVAILABLE **What it means.** That domain cannot be bought here. **What to do.** Show the person the note in details — taken, premium, or an ending the platform does not sell — and offer the alternatives in details.alternatives, or use add_domain with a domain they already own. - HTTP status: 409 - Retryable after applying the fix: no Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "DOMAIN_UNAVAILABLE", "message": "That domain cannot be bought here.", "fix": "Show the person the note in details — taken, premium, or an ending the platform does not sell — and offer the alternatives in details.alternatives, or use add_domain with a domain they already own.", "file": "", "retryable": false, "docs": "https://docs.uarpie.app/errors/DOMAIN_UNAVAILABLE" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # DOMAIN_QUOTE_EXPIRED **What it means.** That price quote is no longer current. **What to do.** Call buy_domain again without quote_id for a fresh quote, show the person the new confirmation sentence, and retry with the new quote_id once they say yes. - HTTP status: 409 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "DOMAIN_QUOTE_EXPIRED", "message": "That price quote is no longer current.", "fix": "Call buy_domain again without quote_id for a fresh quote, show the person the new confirmation sentence, and retry with the new quote_id once they say yes.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/DOMAIN_QUOTE_EXPIRED" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # DOMAIN_ORDER_IN_PROGRESS **What it means.** A domain is already being bought for this app. **What to do.** Wait for it to finish; read it with GET /v1/projects/{id}/domains/orders. One domain order at a time per app. - HTTP status: 409 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "DOMAIN_ORDER_IN_PROGRESS", "message": "A domain is already being bought for this app.", "fix": "Wait for it to finish; read it with GET /v1/projects/{id}/domains/orders. One domain order at a time per app.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/DOMAIN_ORDER_IN_PROGRESS" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # AI_UNAVAILABLE **What it means.** Drafting with AI is not set up on this server. **What to do.** Set ANTHROPIC_API_KEY in the API's environment and restart it. Until then, write the page by hand; nothing else is affected. - HTTP status: 503 - Retryable after applying the fix: no Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "AI_UNAVAILABLE", "message": "Drafting with AI is not set up on this server.", "fix": "Set ANTHROPIC_API_KEY in the API's environment and restart it. Until then, write the page by hand; nothing else is affected.", "file": "", "retryable": false, "docs": "https://docs.uarpie.app/errors/AI_UNAVAILABLE" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # SITE_PAGE_PROTECTED **What it means.** That page is part of the site itself and cannot be deleted or given a different address. **What to do.** Edit its content instead. To take it out of the navigation, turn off nav.show. Only pages created in the console can be deleted. - HTTP status: 409 - Retryable after applying the fix: no Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "SITE_PAGE_PROTECTED", "message": "That page is part of the site itself and cannot be deleted or given a different address.", "fix": "Edit its content instead. To take it out of the navigation, turn off nav.show. Only pages created in the console can be deleted.", "file": "", "retryable": false, "docs": "https://docs.uarpie.app/errors/SITE_PAGE_PROTECTED" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # SITE_SLUG_TAKEN **What it means.** Another page or post already uses that address. **What to do.** Choose a different slug, or edit the existing page instead of creating a second one. - HTTP status: 409 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "SITE_SLUG_TAKEN", "message": "Another page or post already uses that address.", "fix": "Choose a different slug, or edit the existing page instead of creating a second one.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/SITE_SLUG_TAKEN" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # INTEGRATION_NOT_CONFIGURED **What it means.** That figure comes from an outside service that is not connected. **What to do.** Set the environment variables named in details.variables on the API and restart it. - HTTP status: 503 - Retryable after applying the fix: no Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "INTEGRATION_NOT_CONFIGURED", "message": "That figure comes from an outside service that is not connected.", "fix": "Set the environment variables named in details.variables on the API and restart it.", "file": "", "retryable": false, "docs": "https://docs.uarpie.app/errors/INTEGRATION_NOT_CONFIGURED" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # VALIDATION_FAILED **What it means.** The request was malformed. **What to do.** Check details.issues for the fields that failed validation and correct them. - HTTP status: 400 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "VALIDATION_FAILED", "message": "The request was malformed.", "fix": "Check details.issues for the fields that failed validation and correct them.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/VALIDATION_FAILED" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. --- # INTERNAL **What it means.** Something went wrong on the platform. **What to do.** Retry once. If it fails again, report the request_id in details. - HTTP status: 500 - Retryable after applying the fix: yes Every failure from the CLI or an MCP tool has this shape: ```json { "ok": false, "code": "INTERNAL", "message": "Something went wrong on the platform.", "fix": "Retry once. If it fails again, report the request_id in details.", "file": "", "retryable": true, "docs": "https://docs.uarpie.app/errors/INTERNAL" } ``` Apply `fix` (edit `file` when it is given), then run the same command again. ---