# reachpad > Turn your ideas into apps with your coding agent. Reachpad gives each app a link to share, lets you choose who can open it, and keeps earlier versions. reachpad is Google Docs for AI apps: turn your idea into an app with your coding agent, then share it like a document. Make a website, share a document, or build an app people can use, such as a signup form or feedback board. Ask your agent to publish it on Reachpad and it gives you a link to open and share. Choose who can open your app and who can edit it. Update it at the same link, and go back to an earlier version whenever you need to. ## Pages - [Home](https://reachpad.dev): Turn your ideas into apps with your coding agent. Reachpad gives each app a link to share, lets you choose who can open it, and keeps earlier versions. - [Docs](https://reachpad.dev/docs): What an app is, publishing one, sharing it, versions, and connecting Claude or ChatGPT. - [What an app is](https://reachpad.dev/docs/apps): An app is a set of files published to reachpad. It gets a link of its own, a version history, and a setting for who can open it. - [Publishing an app](https://reachpad.dev/docs/publishing): Ask your coding agent to publish over MCP, or upload one file in the browser. Both put version 1 live at a link. A server app also gets a database, a file store and your organization's secrets. - [Sharing and access](https://reachpad.dev/docs/sharing): The three access levels, inviting one person by email address, and what happens to somebody who follows a link they cannot open. - [Versions](https://reachpad.dev/docs/versions): Every publish is a numbered version with a link of its own, and any earlier version can be put back live. - [Connect a chat or an agent](https://reachpad.dev/docs/mcp): One URL, four steps, and the tools a connected chat or coding agent can call. - [Blog](https://reachpad.dev/blog): Posts on running coding agents and on getting what they build in front of another person. - [An agent built it in an hour. Getting it to one other person took the rest of the week](https://reachpad.dev/blog/sharing-what-an-agent-built): The cost of putting software in front of someone else barely moved when the cost of writing it collapsed. What that bill is made of, why it is the same for a tool three people use, and what you can skip. - [Free coding models on every pad](https://reachpad.dev/blog/free-models): Gemma 4 31B, Gemini 3.6 Flash and Gemini 3.7 Flash are included with every pad through OpenCode, with no provider account or API key to configure. - [Gemini 3.7 Flash offered for free](https://reachpad.dev/blog/gemini-3-7-flash): Gemini 3.7 Flash is included with every pad through OpenCode, alongside Gemma 4 31B and Gemini 3.6 Flash. What you get, how to run it, and what the included allowance covers. - [Privacy Policy](https://reachpad.dev/privacy): How reachpad collects, uses, discloses, retains, and protects information. ## Answers ### How do I publish an app? Follow the [setup guide](/docs/mcp) to connect your agent, then ask it to publish your app. It gives you a link to open and share. ### What can I make? A website, a document, a file to share, or an app people can use, such as a signup form, feedback board, or team planner. ### Will my link still work when I update the app? Yes. Your app keeps the same link when you update it. Each saved version also has its own link, so people can still open an earlier version. ### Who can open my app? People in your team can open it by default after signing in. You can choose specific people instead, or let anyone with the link open it. You can also add a password or set when access ends. ### Can I share an app with someone who has no account? Yes. Share a public link so they can open it without signing in. For private access, invite them by email; they can sign in with that address to open it. ### Can I go back to an earlier version? Yes. In your app's version history, choose Restore this version beside the version you want. Your app's link will show that version again, and the newer version will still be saved. ## Contact Write to hello@reachpad.dev to ask about a ceiling above the ones in the manual, or anything the pages above do not answer. --- # The manual Every page of https://reachpad.dev/docs, in full, in the order the sidebar lists them. Each page is also served on its own at /docs/.md. --- # What an app is > An app is the files your coding agent published, answering at a link of its own. Publishing again makes a new version and leaves the old version's link working. Part of the reachpad docs: https://reachpad.dev/docs. Rendered page: https://reachpad.dev/docs/apps. Last checked 2026-09-05. ## The link Every app has a slug, and the app answers at `https://.reachpad.app/`. A slug is the name, lowercased, plus a random 5-character tag: `todo-k8f2m`. The tag is why two people can both call an app Todo and get a working link each, with neither one made to look like a copy. It is at most 40 characters of a-z, 0-9 and single hyphens, and you can pick the readable half yourself. > Note: Two hyphens in a row are reserved for version links: `https://todo--preview.reachpad.app/_rp/preview?version=3` is version 3 of `todo`. A slug cannot contain `--`, and a handful of names a mail server or a browser would ask for first, such as `www`, `api` and `mail`, are not available either. ## What can be in one - A page. HTML, CSS, images and scripts, with `index.html` answering at the root of the link. - A document. Markdown, rendered to a page, with the Markdown kept beside it so a later read gets back what was written. - A file. Anything else, behind a page that names it and its size. - A server. One JavaScript module that exports `default { fetch }`, called for every request to the link. A server app can keep data. It gets a SQL database of its own, a place to store files, and the names of any secrets your organization has set: [storing data in a function](https://reachpad.dev/docs/publishing#storing-data). A page has none of those, because it is files on a link with no code to hold a credential. The badge on the row in your list (app, page, doc or file) is a label for the people reading that list. What actually answers a request is set on each version: `page` serves the files, `function` runs the module. An app that shipped a page in version 1 and a server in version 2 is still one app, on one link. ## Where your apps are listed [reachpad.dev/apps](https://reachpad.dev/apps) lists every app in your organization, newest first, with folders, starred apps, a shared-with-me list and a trash. Your own organization comes from your email address: a company address puts you with everyone else at that domain, a personal address gets an organization of its own. Accept an invitation to another one and you are in both; switch between them from the organization name at the top of the sidebar. The CLI and any connected chat work in whichever one you have current. Reachpad asks you to verify your email address before you can publish or be given access to anything, because an invitation names a mailbox and nothing else. Folders hold apps and other folders. Deleting a folder never deletes an app: a folder with anything still in it refuses to be deleted, so move the contents out first. Moving an app to the trash stops its link answering. Restoring it puts the link back. Deleting it from the trash removes the app and its versions for good. ## The ceilings | Limit | Value | | --- | --- | | Apps in one organization | 200 | | New versions of one app | 60 an hour | | People one app is shared with | 100 | | Files handed over inside a publish call | 2 MB in total | | A snapshot uploaded instead of handed over | 50 MB | | A function answering one request | 90 seconds, at most 10 of them CPU | Each refusal names its own number, so a call that hits one says which ceiling it hit. What a server app can store is a separate set of numbers: [storing data in a function](https://reachpad.dev/docs/publishing#storing-data). --- # Publishing an app > Two ways in. Your coding agent calls the reachpad MCP server and hands over the files, or you upload one file and reachpad publishes it for you. A server app can also store data: it has a database of its own, a place for files, and the secrets your organization has set. Part of the reachpad docs: https://reachpad.dev/docs. Rendered page: https://reachpad.dev/docs/publishing. Last checked 2026-09-05. ## Through your coding agent Connect the agent to `https://reachpad.dev/mcp` once, which is [one URL and four steps](https://reachpad.dev/docs/mcp), then ask it to publish. It calls `create_app` and gets the link back in the answer. - `name` is what your list shows. - `files` is an array of `{ path, content }`, with the content written as text rather than encoded. - `slug` picks the subdomain. Leave it out and it comes from the name. - `manifest` says how the version runs: `{ "kind": "page", "entry": "index.html" }`, or `{ "kind": "function", "entry": "server.js" }` for a server, which can also declare the `services` and `secrets` it uses. Omit the manifest for a page, or read [the manifest](https://reachpad.dev/docs/publishing#the-manifest) for every field. - `access` sets who can open the link. Leave it out and it is anyone signed in to your organization. > Note: Version 1 is live the moment `create_app` answers. There is no staging step and no draft, so ask your agent to show you the content and the destination before it calls. ## Publishing a change Have the agent call `read_app` first. It answers with `base_version_id`, the version the next edit builds on, and that version's file listing. Pass it to `update_app` as `base_version_id`. If somebody else published in between, `update_app` refuses with `stale_base` rather than dropping their version. The move after that refusal is to read the app again and pass the `base_version_id` it returns. A publish replaces the whole file set. A file that was in the last version and is not in this call is not in the app any more. To send only what changed, pass `mode: "merge"`. The new version keeps every file of the version `base_version_id` names that the call does not name, and `remove` takes the paths to drop. The 2 MB limit on a publish counts the carried files too. `validate: true` on either call answers with the file set it would publish, its size and its entry file, and writes nothing. Use it to find a missing `index.html` before the link serves a 404 instead of after. `publish: false` on `update_app` builds the version and leaves the link where it is. The staged version has its own URL to look at, and `publish_app_version` puts it live. The same call takes an older number, which is how an app is rolled back. New versions go live as they are published, up to 60 an hour for one app. ## Uploading a file [Add an app](https://reachpad.dev/apps/new) takes one file and publishes it as version 1 of a new app. - A Markdown file is rendered to a page, and the Markdown travels with it. - An HTML file is served as it is. - Any other file gets a page naming it and its size, with the file itself behind that page. The same screen writes a build prompt you can paste into a coding agent that has your project folder open, for the times when the thing you want published does not exist yet. ## The manifest The manifest is one object on a publish call, and it decides how the version runs. Leave it out and the version is a page. | Field | What it is | | --- | --- | | `kind` | `page` serves the files. `function` runs one module for every request. | | `entry` | For a page, the file served at the root of the link, usually `index.html`. For a function, the module that exports `default { fetch }`. | | `env` | Plain string values the function reads as `env.NAME`. They are stored as written and readable by anyone who can read the version, so a key belongs in `secrets` instead. | | `services` | What the function may use: `db`, `files`, or both. A function only. A name outside that pair is refused when you publish rather than at the first call. | | `secrets` | The names of organization secrets to bind, each arriving as `env.NAME`. A function only. | ```json { "kind": "function", "entry": "server.js", "env": { "SITE_NAME": "Notes" }, "services": ["db", "files"], "secrets": ["STRIPE_KEY"] } ``` Over MCP the manifest is an argument on the publish call. From the command line it is the same object, kept in `reachpad.json` beside the source, which is the file the refusals name. On `update_app`, leaving the manifest out keeps the previous version's. Sending one replaces the whole of it, so a manifest that omits `services` takes `env.reachpad` away from code that still calls it. A function is handed whoever opened the link. `request.reachpad` is `{ user, name, org, role, app, version }`, so an app that its readers sign in for can key its rows on `user` without asking them to sign in twice. > Note: On a public link nobody has to sign in, and an anonymous reader arrives with `user` and `name` set to `null` and `role` set to `anonymous`. `org` is the organization that owns the app either way. Check `role` before you trust `user` to be there. ## What a function should do in a request A function answers one request and returns one answer. It gets about 10 seconds of computing per request, and 90 seconds before the link stops waiting. Computing is time your code spends working: waiting on the database, on a file, or on a service you call costs none of the 10 seconds, which is why an app that streams a model's answer for a minute is well inside both. Almost nothing an app does in a request comes close. A page put together from a few queries is a handful of milliseconds of computing. Work that runs for minutes belongs somewhere else: a long render, a large export, a job that walks a whole dataset. Hand it to a service built to run it, keep a row for the job in the app's database, answer straight away with its id, and let a later request report on it. An app that tries to finish that work inside the request fails in front of whoever opened the link. ## Storing data in a function `env.reachpad` is on every function. What `services` decides is whether a call through it works: a version that has not declared `db` still has `env.reachpad.db.query`, and the call fails when it is made. There is nothing to install and no connection string to hold. ```js const { rows } = await env.reachpad.db.query( "SELECT id, title FROM notes WHERE owner = ? ORDER BY id DESC LIMIT 50", [request.reachpad.user], ); const id = crypto.randomUUID(); await env.reachpad.files.put(id, await request.arrayBuffer(), "image/png"); const stored = await env.reachpad.files.get(id); ``` - `db.query(sql, params)` answers `{ rows, changes, lastInsertRowid }`, with each row an object keyed by column name. - `db.batch([{ sql, params }])` runs 1 to 20 statements in one transaction and answers an array of the same `{ rows, changes, lastInsertRowid }`, one per statement, in the order they were sent. A statement that fails takes the whole batch back with it. - A parameter is a string, a number, a boolean, `null`, or `{ "$blob": "" }` for bytes. A `BLOB` column reads back in that same shape, so what you wrote is what you get. - Each app has one database, shared by every version of that app. A version staged for review reads and writes the same rows the live one does. - `files.put(id, body, contentType)` takes an id your app picks, a lowercase UUID, and a body that is a string, an ArrayBuffer, a view or a Blob. `files.get(id)` answers a response you can return as it is, `files.head(id)` answers `{ bytes, contentType, etag }`, and `files.delete(id)` removes it. `get` and `head` answer `null` for an id that is not there. - There is no call that lists files. Your app chose the ids, so your own table is the index. - `usage()` answers `{ db_bytes, files, file_bytes }`, which is what to read before storing something large. | Limit | Value | | --- | --- | | One app's database | 10 GB, the storage platform's own ceiling | | Rows one query or batch returns | 5,000, or 4 MB of JSON | | Statements in one batch | 20 | | One stored file | 100 MB | | Files in one app | 10,000, and 1 GB in total | | Migration files in one version | 200, each at most 100 KB | | Secrets in one organization | 100 | > Note: A query that would return more than 5,000 rows or 4 MB is refused rather than truncated: `The result is over 5,000 rows or 4 MB. Add a LIMIT.` From the command line, `reachpad db "SELECT count(*) FROM notes"` runs one statement against the same database, with `--params '[...]'` for its bound values. It refuses a schema change: those go in a migration file. ## Secrets A secret belongs to your organization, not to one app. Set `STRIPE_KEY` once and every function app in the organization can bind it by naming it in `secrets`. Set one in [Settings](https://reachpad.dev/apps/settings/secrets), or with `reachpad secrets set STRIPE_KEY`, which reads the value from a hidden prompt or from what you pipe into it and never from the command line. A name is capital letters, digits and underscores, starting with a letter, at most 64 characters, and cannot start with `RP_`. Set the secret before you publish the app that names it. A name your organization has not set fails the publish with `STRIPE_KEY is not set. Run reachpad secrets set STRIPE_KEY.`, and a name written into both `env` and `secrets` fails with `STRIPE_KEY is both an env var and a secret. Keep one.` `reachpad secrets list` shows the names and which apps bind them, never the values. Nothing reads a value back out, including you. `reachpad secrets remove` clears the value from the running versions that bind it, within seconds, so removing one takes it away from apps published months ago. It reports any version it could not update, and the name stays listed until they are all done, so the next remove retries exactly those. ## Changing the schema Schema changes live in the app, in `migrations/NNNN_name.sql`: four or more digits, an underscore, then lowercase letters, digits, hyphens or underscores. `migrations/0001_notes.sql` and `migrations/0002_add_pinned.sql` run in that order. They run when the version goes live, not when it is built. That is the point of the split: an app has one database, so a version staged with `publish: false` would otherwise rewrite the schema under the version still serving. - Only a function that lists `db` in `services` may carry the folder. Anything else fails the publish with `Migrations need the db service.` - Never edit a file that has already been applied. Each file's hash is recorded, and a changed one is refused: `migrations/0002_x.sql changed after it was applied. Add a new file instead.` - One file is all or nothing, and a set of them is not. A version carrying three files can stop at the second, and the first one stays applied. - A file that fails fails the publish, with the message SQLite gave, and the app's link keeps serving the version it was serving. - A file takes no bound values, and 200 files of at most 100 KB each is the ceiling for one version. The record of what ran is a table in your own database, `_rp_migrations`. Read it with `reachpad db "SELECT name, applied_at, version_id FROM _rp_migrations"`, fix the file that failed, and publish again: anything already applied is skipped. ## What the link serves after a publish The app's own link always serves whichever version is live. Each version also keeps a link of its own that never changes what it serves: [versions and their links](https://reachpad.dev/docs/versions). --- # Sharing an app and who can open it > Who can open an app is one setting with three values, plus a list of people invited by email address. The owner decides both. Part of the reachpad docs: https://reachpad.dev/docs. Rendered page: https://reachpad.dev/docs/sharing. Last checked 2026-09-04. ## The three levels | Level | Who can open the link | | --- | --- | | Restricted | The owner, and the people named on the app. | | Organization link | Anyone signed in to reachpad who is in your organization, plus the people named on the app. | | Public link | Anyone who has the URL. | A new app is an organization link. Only the owner can change the level, from the app's page or with the `set_app_access` tool. > Note: An organization link is not private to the people you sent it to. Anyone at your company who is signed in and has the URL can open it. A public link can carry a password, and a date after which it stops answering. Both apply to a public link only, and either can be cleared later. ## Inviting one person `share_app` takes an email address and a role. A viewer can open the app. An editor can also publish new versions and, when the owner enables `Editors can change permissions and share`, invite people, remove access, and change viewer/editor roles. The address gets an email naming who shared it, what it is, and the link. An address with no reachpad account still gets the grant. It starts working the first time somebody signs in holding that address, so you can invite a colleague before they have an account. `list_app_shares` shows everyone who has been added, their role, and whether their grant has attached to an account yet. The owner and permitted editors can use it. `revoke_app_share` removes one of them and touches nothing else; permitted editors can use it too. Editors cannot transfer ownership or change general access, which remains owner-only. The owner can turn off editor-managed sharing for an app, and one app can be shared with 100 people. ## When somebody cannot open it A signed-in person who follows a link they have no access to gets a page naming the app and a button that asks the owner for access. Reachpad accepts one ask per person per app per day, so a refresh does not become a way to fill somebody's inbox. The page names the app on purpose: they arrived by following a link somebody sent them, so the app's existence is not the thing being withheld. --- # Versions and their links > Every publish makes a version, numbered from 1 and never edited afterwards. Each one keeps a link that goes on serving what it served the day you sent it. Part of the reachpad docs: https://reachpad.dev/docs. Rendered page: https://reachpad.dev/docs/versions. Last checked 2026-09-04. ## Every publish is a version A version carries the message written with it, who published it, when, how many files it holds and how large they are. Nothing rewrites one afterwards: a change is the next version, not an edit of this one. `list_app_versions` reads them newest first and pages through with the `next_cursor` it hands back. `read_app_version` reads one by its number, with the file listing of what that publish contained. A version that fails to build keeps its number and stays in the history as failed, so the next publish is the number after it rather than a retry of the same one. ## A link for each version Version 2 of `todo` answers at `https://todo--preview.reachpad.app/_rp/preview?version=2`, and version 3 at `https://todo--preview.reachpad.app/_rp/preview?version=3`. The app's own link, `https://todo.reachpad.app/`, serves whichever version is live. A version's link keeps working after a later version goes live. Somebody you sent version 2 to last week still sees version 2, which is the difference between showing a colleague what you built and showing them what it has become since. Earlier versions open for the app's editors; the live link follows the app's access setting. ## Putting an earlier version back Restore this version, on the app's page, makes an earlier version the live one. It moves a pointer: nothing is rebuilt, nothing is copied, and the version you moved away from keeps its own link and its own number. A version that failed to build cannot be made live, and neither can one whose files have not finished reaching the app host. The row says which of the two it is. --- # Connecting Claude or ChatGPT to reachpad > Reachpad runs a hosted MCP server. Connect a chat or a coding agent to it once, and it can publish apps to your account and read the ones you can already see. Part of the reachpad docs: https://reachpad.dev/docs. Rendered page: https://reachpad.dev/docs/mcp. Last checked 2026-09-08. ## The endpoint The endpoint is `https://reachpad.dev/mcp`. Streamable HTTP, POST only. Authorization is OAuth 2.1 with dynamic client registration, so there is no client id, no secret and no callback to copy anywhere: the client registers itself and you approve it on a consent screen. A client that wants the mechanics can read [auth.md](https://reachpad.dev/auth.md). ## Connecting a chat - In Claude or ChatGPT, open Customize, then Connectors. - Add a custom connector and paste `https://reachpad.dev/mcp`. - Sign in to reachpad on the consent screen and choose your organization. The chat gets the access you have, and no more. - Start a new chat, turn reachpad on, and ask it to publish something small. > Note: ChatGPT adds a custom connector only from a Business or Enterprise workspace with developer mode turned on, and only on the web. That is ChatGPT's rule, and nothing on the reachpad side changes it. The app appears in [your list](https://reachpad.dev/apps) with a link the moment the chat publishes it. [Connect Claude or ChatGPT](https://reachpad.dev/apps/settings/connect) has the same four steps beside a copy button. ## Connecting a coding agent A coding agent that speaks MCP takes the same URL. These three were run against the hosted endpoint rather than read off their config syntax. ```bash claude mcp add --transport http --scope user reachpad https://reachpad.dev/mcp claude mcp login reachpad codex mcp add reachpad --url https://reachpad.dev/mcp codex mcp login reachpad opencode mcp add reachpad --url https://reachpad.dev/mcp opencode mcp auth reachpad ``` Codex starts the sign-in as part of adding the server. Claude Code and OpenCode store the server first and need the login command after it. Restart the client, then ask it to list your apps. ## What a connected client can call | Tool | What it does | | --- | --- | | `whoami` | The account and organization this connection is signed in as. Takes no arguments, and every app it creates lands in that organization. | | `search_apps` | Match text against the name and the slug of apps in your organization. | | `read_app` | One app: its access level, its live version and that version's id, and its files with each text file's content, up to 256 KB a file and 1 MB an answer. | | `read_app_file` | One file's text, for a file `read_app` left out for its size. Up to 256 KB. | | `read_app_preview` | A picture of what the app's link serves right now. Use it for a function app, whose module stays with the people who develop it. | | `create_app` | Publish new files as an app and get the link back. | | `update_app` | Publish a new version, against the `base_version_id` that `read_app` returned. `mode: "merge"` sends only the changed files, `validate: true` checks the call without publishing, `publish: false` stages it. | | `publish_app_version` | Make a version live by its number: a staged one, or an older one to roll back to. | | `list_app_versions`, `read_app_version` | The version history, and one version's contents. | | `set_app_access` | Restricted, organization link or public link, with a password or an expiry on a public one. | | `share_app`, `list_app_shares`, `revoke_app_share` | Invite one email address as a viewer or an editor, read the list, remove one. | | `ls`, `tree`, `mkdir`, `mv`, `rmdir` | Folders. `rmdir` refuses a folder that still holds anything, and never deletes an app. | | `trash_app` | Move an app to the trash, which stops its link. Owner only, and restoring or deleting it for good is done in Trash. | | `query_app_db` | One SQL statement against a function app's database, for reading rows and fixing data. Owners and editors only, and schema changes are refused: those go in the version's migrations. | | `list_secrets`, `set_secret`, `remove_secret` | The organization's secrets by name. A value is sealed on write and no tool returns it. | | `get_app_logs` | Recent log lines from a server app. An app that serves files has none and says so, and function logs are not captured yet. | Listing the tools takes no credential, so a client can describe reachpad before you have signed in. Calling one takes a token, and every call is made as you: a connector can reach the apps you can reach and no others.