# Crow Nest docs Crow Nest is where the web apps people build with AI live. You build something with Claude, Codex or Cursor, ask your AI to publish it, and it gets its own address at `https://.crow-nest.com`. You choose who can open it, the way you share a document: only you, specific people, anyone signed in with the link, your whole company, or the public. Each app can save data, take file uploads, call an AI model, call outside APIs with secret keys, update live for everyone looking at it, and run its own server code. None of that needs a backend you run, and no key ever sits in the page. ## Start here - [Quickstart](/quickstart): connect your AI to Crow Nest and publish your first app in a few minutes. - [How Crow Nest works](/how-it-works): apps, addresses, versions, sign-in and workspaces. - [For AI agents](/for-ai-agents): how an AI should read these docs and use the MCP server. ## Publish and share - [Publishing](/publishing): from the MCP server, the `crow` CLI, or a framework project. - [Sharing and access](/sharing): the five ways to share, and what viewers see. - [Custom domains](/custom-domains): put an app on your own address, or buy a domain through Crow Nest. ## Build apps - [crow.json](/crow-json): the one file that turns features on and sets the rules. - [Data](/data), [Files](/files), [Live updates](/live), [AI calls](/ai), [Outside APIs and secrets](/apis-and-secrets). - [Server code](/server-code): webhooks, SQL, scheduled jobs, background tasks and logs. ## Reference - [MCP server and tools](/mcp), [CLI](/cli), [Limits](/limits), [Error codes](/errors). ## Accounts and billing - [Organizations and companies](/organizations), [Plans and billing](/pricing), [Security and trust](/security). ## Getting help Email [support@crow-nest.com](mailto:support@crow-nest.com) with questions, problems or anything to report. > **Tip** Every page is also plain Markdown: add `.md` to its address (this one is [/index.md](/index.md)). [llms.txt](/llms.txt) lists every page, and [llms-full.txt](/llms-full.txt) has all of them in one file. --- # Quickstart You need an AI coding tool that can use MCP servers (Claude Code, Claude Desktop, Codex or Cursor) and a Google, Microsoft or email account to sign in with. ## 1. Connect the MCP server Paste this line into your AI: ```text Connect Crow Nest: https://crow-nest.com/mcp, then tell me what I can do with it. ``` Coding agents like Claude Code add the server themselves. Chat apps like Claude and ChatGPT can't add servers from a chat, so they tell you the few clicks to do it. Those steps, and the ones for every other app, are in [Connecting your AI](/mcp#connecting-your-ai). The first time your AI uses it, a browser window opens so you can sign in to Crow Nest and approve the connection. That's your account: there's nothing else to set up. ## 2. Build something Ask your AI to build a small web app, for example: "Make a page where my team can add ideas and vote on them." Anything that ends up as HTML, CSS and JavaScript works, including Vite, React, Next.js (static export), Astro, SvelteKit and Nuxt projects. ## 3. Publish it Say "publish this to Crow Nest". Your AI calls the `publish` tool, which asks you two things: - **A name.** It becomes the address, like `idea-board.crow-nest.com`. - **Who can see it.** Crow Nest never picks for you. Choose one of: only you, specific people, anyone signed in with the link, your company or organization, or the public. A few seconds later the app is live and your AI gives you the link. ## 4. Change it and publish again Ask for changes and publish again. Every publish is a new version at the same address; older versions are kept, and `rollback` brings one back. ## 5. Share it Ask your AI to "share it with sam@example.com" or "make it public", or use the Share button on the app's page in the [dashboard](https://app.crow-nest.com/). ## Where to go next - Save data people add: [Data with crow.db](/data). - Let people upload files: [Files](/files). - Call an AI model from the app: [AI calls](/ai). - Run code on a server, on a timer or for a webhook: [Server code](/server-code). - Every setting in one place: [crow.json](/crow-json). --- # How Crow Nest works Crow Nest hosts static web apps and gives each one the services it would otherwise need a backend for. This page explains the parts you'll meet everywhere else in the docs. ## Apps and addresses An app (the dashboard and the API sometimes call it a tool) is a folder of web files: `index.html`, scripts, styles and images, plus an optional `crow.json` and an optional `server/` folder. Publishing uploads the folder; Crow Nest serves it at `https://.crow-nest.com`. - The name is chosen when you first publish and never changes. Some names are reserved (`www`, `app`, `api`, `docs`, `mail` and similar). - An app can also have [custom domains](/custom-domains), like `booking.mybakery.com`. It stays at its crow-nest.com address too. - Files are served from Cloudflare's network, cached at the edge after the access check. ## Versions Every publish creates a new version and makes it current. Old versions stay, so you can go back with `rollback` (MCP), the dashboard, or the API. A rollback doesn't create a new version; it points the app at the older one. ## Viewers and sign-in Everyone who opens an app that isn't public signs in to Crow Nest first, with Google, Microsoft, or a one-time code sent to their email. Sign-in happens once on `crow-nest.com` and carries over to each app address, so an app never handles passwords or tokens itself. Inside the app, `crow.me()` tells the page who is looking. Rules in `crow.json` decide what each person can read and change. See [Sharing and access](/sharing) for who can open an app, and [Data](/data) for who can read and write its data. ## Workspaces Every app belongs to a workspace: | Workspace | Who | Billing | | --- | --- | --- | | Personal | One person's own apps | The person, on the personal plans | | Organization | A group someone created themselves and invited people to | The organization's owner, on the personal plans | | Company | A company Crow Nest set up, behind its own login | Per seat | Organizations and companies share an app list, can share apps with everyone inside (`org` visibility), and have policies, shared AI keys and admins. See [Organizations and companies](/organizations). ## What Crow Nest runs for you | You need | Use | Page | | --- | --- | --- | | Save and share data | `crow.db` collections with rules | [Data](/data) | | File uploads | `crow.files` buckets | [Files](/files) | | Updates that appear for everyone | `watch` and `crow.live` channels | [Live updates](/live) | | AI answers | `crow.ai` with your or your company's key | [AI calls](/ai) | | A third-party API with a secret key | `crow.api` | [Outside APIs and secrets](/apis-and-secrets) | | Webhooks, SQL, timers, background work | `server/index.js` | [Server code](/server-code) | All of these come from one script your page loads, `/__crow/sdk.js`, served by the app's own address. ## What Crow Nest doesn't do - It doesn't build your project on its servers. Build on your own machine (the `crow` CLI does it for you) and publish the output. - It doesn't supply AI. Apps call models with keys you, your company or the app brings. - It doesn't run long-lived servers or WebSockets in your server code. --- # For AI agents These docs are written for people and for the AI agents working with them. If you are an agent building or publishing an app on Crow Nest, this page is for you. ## Reading the docs - Every page is plain Markdown at the same address plus `.md`, for example [https://docs.crow-nest.com/data.md](/data.md). - [llms.txt](/llms.txt) lists every page with a one-line summary. [llms-full.txt](/llms-full.txt) is every page in one file, in reading order. - The MCP server's `crow_docs` tool returns the developer reference (the browser library, `crow.json` rules and server code) without leaving your session. It's also at `https://crow-nest.com/mcp/docs`. Read the reference before writing code that uses Crow Nest. Don't guess method names, and don't refuse a task for lack of documentation. ## Connecting The MCP server is at `https://crow-nest.com/mcp` (streamable HTTP, OAuth sign-in in the person's browser). The person signs in once; your session then acts as them. See [MCP server and tools](/mcp) for every tool. ## Rules to follow - **Publish the built output**, not the source project: `dist` for Vite and Astro, `build` for Create React App and SvelteKit, `out` for Next.js with `output: "export"`, `.output/public` for Nuxt, `build/client` for Remix in SPA mode. - **Ask who should see a new app.** Never choose the visibility yourself. The `publish` tool asks the person directly when your client supports it; otherwise it tells you to ask and call again. - **Never put a secret in the app's files** or in a tool argument. Declare outside APIs in `crow.json` and use `tool_secret`, which gives the person a dashboard link to paste the value. For AI keys use `ai_key`. Publishing refuses files that contain keys. - **Use crow.db for data**, not your own backend or a third-party database with keys in the page. - **Bundle server code.** `server/index.js` is uploaded as is; bare npm imports are refused. - **Never take card details.** For billing questions use the `billing` tool, which gives a Stripe link when the person has to act. - **Confirm before deleting.** Use `delete_tool` only when the person asks; it asks them to confirm. ## When something goes wrong - Errors carry a `code`. See [Error codes](/errors). - `tool_data` shows what an app has saved and its rules; `tool_logs` shows the server code's recent log lines; `tool_stats` shows opens. - A `card_required` answer on publish means the person needs to add a card first: give them the Settings link from the message. - If something keeps failing, or the person needs a human, point them to support@crow-nest.com. --- # Publishing Publishing uploads a folder of web files and makes it the app's current version. Today you publish through your AI with the MCP server; the `crow` command-line tool is coming. ## From your AI (MCP) Ask your AI to publish. It calls the `publish` tool with the built folder's files. For a new app it asks you for a name and who should see it; for an existing app it publishes a new version to the same address. Your AI should build the project first (see the table below) and publish the output folder. ## From the terminal (crow CLI, coming soon) > **Note** The `crow` CLI isn't on npm yet. Until it is, publish through your AI. This section describes how it will work. ```bash npm install -g crow-nest crow login # opens crow-nest.com/device to sign in crow publish # in your project folder ``` `crow publish` recognizes Vite, React, Next.js, Astro, SvelteKit, Nuxt and Remix projects, runs the build on your machine, checks the output will work on Crow Nest, and publishes it. The first publish asks for a name and visibility and writes `.crow.json` with the app's ID, so later publishes update the same app. See [CLI](/cli) for every command. ## What to publish Publish the **built output**, the folder with `index.html` in it: | Project | Folder | Notes | | --- | --- | --- | | Plain HTML | the folder itself | | | Vite, Astro | `dist` | Vite `base` must be `/` or `./` | | Create React App, SvelteKit | `build` | SvelteKit needs `@sveltejs/adapter-static` | | Next.js | `out` | Needs `output: "export"` in `next.config.js` | | Nuxt | `.output/public` | From `nuxt generate` | | Remix | `build/client` | SPA mode | Put `crow.json` at the root of that folder if the app uses data, files, AI, APIs, live updates or server code. Put server code in `server/`; it's never served to browsers. ## An icon is required Every app has an icon. It's the app's favicon in the browser tab, and how it shows on your dashboard and in your organization's app list. Publishing without one is refused with `icon_required`. Crow Nest looks for it in this order: 1. The `icon` key in `crow.json`. 2. The first `` (or `shortcut icon`, `apple-touch-icon`) in the root `index.html` that points at a file you publish. 3. `icon.svg`, `favicon.svg`, `favicon.png`, `favicon.ico` or `icon.png` at the root. SVG, PNG, ICO and WebP work, up to 256 KB. PNG and WebP must be square and at least 32 pixels. An SVG needs a `viewBox` and can't hold scripts, ``, `on*` attributes, `javascript:` links or links to other sites. If your folder has no icon, your AI can make a simple square SVG for you. Apps published before icons were required show a letter on a colored square until their next publish. ## Checks on every publish The same checks run whether you publish with MCP or the CLI: - **No secrets in files.** API keys and tokens in the app's files are refused. Use [secrets](/apis-and-secrets) instead. - **A valid crow.json.** Unknown keys and invalid rules are refused with a message that says what to fix. - **Server code is bundled.** Bare npm imports in `server/` are refused; bundle them first. - **Sizes.** Up to 300 MB unzipped and 25,000 files per app; a single upload through the CLI is up to 100 MB zipped. If a check fails, nothing is published and the previous version stays live. ## Versions and rollback Every publish is a new version. To go back, ask your AI to roll back (the `rollback` tool), use the dashboard, or call `POST /tools/:id/rollback`. Rolling back also brings back that version's server code and schedules. ## A card before your first publish A personal account needs a card on file to publish. New accounts add one when they sign up (Pay as you go charges nothing until your apps use paid services). If there isn't one, publishing answers `card_required` with a link to Settings, where you can add it. See [Plans and billing](/pricing#adding-a-card). ## Deleting an app Ask your AI to delete it (the `delete_tool` tool asks you to confirm) or use the dashboard. The app stops working for everyone and its address can't be reused. Crow Nest staff can restore its data for 30 days. --- # Sharing and access Every app has one visibility that decides who can open it. You choose it when you first publish; Crow Nest never picks a default. ## The five visibilities | Visibility | Who can open it | Sign-in | | --- | --- | --- | | `private` | Only you | Yes | | `people` | You and the email addresses you add (up to 200) | Yes | | `link` | Anyone who has the link and signs in | Yes | | `org` | Everyone in your organization or company | Yes | | `public` | Anyone on the internet | No | `org` is only for apps in an organization or company. ## Changing who can see an app - Ask your AI ("share it with sam@example.com", "make it public"). It uses the `share` tool. - In the [dashboard](https://app.crow-nest.com/), open the app and use Share. Changes reach every viewer within 60 seconds, including people who already have the app open. ## What viewers see People who can't open an app see a page that says so, with **Request access** (which emails the owner) and **Switch account**. Signed-out viewers of a non-public app are sent to sign in and straight back. On `public` apps, a small "Made with Crow Nest" badge with a Report link sits in a corner of the page; other visibilities have no badge. ## Company and organization policies Admins of an organization or company can set policies for apps inside it: - whether apps may be `public`, - whether apps may be shared by `link`, - whether apps may be shared with emails outside the company's domains. A share the policy doesn't allow is refused with a message saying so. Organization managers can also pause an app, which shows viewers a page saying the organization paused it. ## Search engines and AI crawlers Public apps are hidden from search engines and AI crawlers by default. To let them be found, turn on `aiDiscoverable` for the app (ask your AI, or use the dashboard). It only works on public apps, and changing the app away from public turns it off. ## Inside the app Visibility decides who opens the app. What each person can read and change inside it is set per collection in [crow.json](/crow-json): see [Data](/data) and [Files](/files). --- # Custom domains An app always lives at `https://.crow-nest.com`. You can also put it on your own address, like `booking.mybakery.com`. ## Use a domain you own 1. Ask your AI to put the app on your address (the `custom_domain` tool), or open the app in the dashboard and go to Domains. 2. Add the DNS records it gives you at your domain's DNS provider. Usually that's one record: | Type | Name | Value | | --- | --- | --- | | CNAME | `booking` (your subdomain) | `domains.crow-nest.com` | If your domain is already on another Cloudflare account, you'll also get a `TXT` record to prove you own it. 3. Crow Nest checks every few minutes and issues a certificate. The domain goes from waiting for DNS, to waiting for the certificate, to live. A domain still waiting after 7 days is marked failed with the reason. Rules for the hostname: - It must be a subdomain with a name in front (`booking.mybakery.com`), not a bare domain (`mybakery.com`). To use a bare domain, buy it through Crow Nest (below). - It can't be under `crow-nest.com`, and names that imitate well-known brands are refused. Everything works the same on a custom domain: access checks, sign-in (which still happens on crow-nest.com and comes back), data and server code. Pages get a canonical link pointing at your domain. ## Put your website on Crow Nest Your company's or your own website can live on Crow Nest at your domain. Ask your AI to publish it and put it on your domain; it follows these steps. 1. The site is published like any app, as **public**. To let search engines and AI assistants find it, also turn on `aiDiscoverable` (public apps are hidden from them by default). 2. **If you don't have the domain yet**, buy it through Crow Nest (below). The site is served on the bare domain (`mybakery.com`) with `www` redirecting to it, and there are no records to add. 3. **If you already own the domain**, connect `www.mybakery.com` (the bare domain can't be connected) and add the CNAME record for `www` shown above. Then, at your registrar or DNS provider, set up forwarding (often called "domain forwarding" or "URL redirect") from `mybakery.com` to `https://www.mybakery.com`. 4. Change only the `www` record and the forwarding. Leave your MX and TXT records (SPF, DKIM, DMARC) alone: they carry your email. If a site already lives at `www`, visitors move to the new one as soon as the record changes. A contact or booking form needs no backend: it saves to the app's database (see [Data](/data)). ## Company domains For companies and organizations, IT can set up one domain for every app, like `expenses.apps.acme.com`, so the people who build apps never touch DNS. Each app's address is its name on the company domain. 1. A company admin (or an organization's owner or manager) opens **Company domain** in the dashboard, or asks their AI (the `company_domain` tool), and enters a subdomain with at least three parts, like `apps.acme.com`. 2. They add two DNS records once, each with a copy button: | Type | Name | Value | | --- | --- | --- | | CNAME | `*.apps.acme.com` | `domains.crow-nest.com` (DNS only, no proxy) | | TXT | `_crow-nest.apps.acme.com` | `crow-nest-verify=` | 3. Crow Nest checks every few minutes, or when you press **Check**, and marks the domain live. If it's still waiting after 7 days it's marked failed and says which record is missing. If you bought the domain through Crow Nest, it's live at once and there are no records to add. Then every app in the company gets its address. With the default setting, **every app gets one automatically**, new apps on their first publish. With **each app's owner turns theirs on**, an app gets one when its owner (or an admin) switches it on, on the app's page. - The app keeps its `crow-nest.com` address; both work, and pages on the company address have a canonical link to it. - Access checks, sign-in, data and server code work the same as on any custom domain. - If your own DNS is on Cloudflare, Cloudflare may ask for one extra `TXT` record per app. They're all listed together under Company domain, with copy buttons. - A company domain can have up to 500 app addresses. - An app loses its address when it's deleted, moved out of the company, or switched off. Removing the company domain removes every address on it. Your AI sees an app's company address in the replies from `publish`, `list_tools` and `share`. ## Buy a domain through Crow Nest Personal accounts can buy domains, and so can the owner and managers of an organization. Organization members ask one of them. Companies Crow Nest set up use their company domain, or email support@crow-nest.com to buy one through us. 1. Ask your AI to find a domain (the `find_domain` tool). It shows up to six options with the first-year and renewal prices. 2. Pick one. You're asked for the owner's contact details and to confirm the price; your card on file is charged. Your AI can never confirm a purchase for you. 3. Crow Nest registers it, creates its DNS, issues a certificate and, if you chose an app, serves the app on the bare domain with `www` redirecting to it. This sets itself up and usually takes a few minutes, sometimes a couple of hours. > **Important** The first time you buy a domain with an email address, the registrar (Name.com) emails you a link to confirm you're the owner. This is an ICANN rule. Click it within 15 days, or the domain is paused until you do. Later domains on the same email don't need it. The dashboard and your AI remind you until then. Domains cost the registrar's price plus $1 a year and renew automatically each year with your card. To stop a renewal, press **Cancel renewal** on the domain in the dashboard's Domains page, or ask your AI; the domain keeps working until it expires, and **Turn renewal back on** undoes it. You're emailed 30 days before a renewal; if a renewal fails you get a link to fix the card, and it's retried daily. `domain_status` (or the dashboard) shows where a domain is: registering, DNS, certificate, live, and when it renews. You can search up to 10 times a minute. If you search faster, you're told how many seconds to wait. When the registrar is busy, a result can say "check at purchase": the name looks free and the price is the usual one, but it is confirmed when you buy, and you're asked again if the price differs. If nothing can be shown, you're asked to try again in a minute. A domain you bought stays yours, even if you delete your Crow Nest account. --- # crow.json `crow.json` sits at the root of the folder you publish. It's optional: an app without it is served as plain web files. Each top-level key turns on one feature and sets its rules. Publishing checks the file and refuses anything invalid, with a message that says what to fix. ## A complete example ```json { "data": { "tasks": { "read": "viewers", "write": "creator" }, "scores": { "read": "public", "write": "public-add", "fields": { "name": "string:30", "score": "integer" }, "dailyLimit": 2000 } }, "files": { "receipts": { "read": "creator", "write": "viewers", "maxSize": "10MB", "types": ["image/*", "application/pdf"] } }, "ai": { "who": "viewers", "perViewerPerMinute": 10, "dailyLimit": 500 }, "apis": { "stripe": { "url": "https://api.stripe.com/v1/", "headers": { "authorization": "Bearer {{STRIPE_KEY}}" }, "who": "viewers" } }, "live": { "who": "viewers" }, "server": { "who": "viewers", "publicPaths": ["/api/webhooks/stripe"], "schedules": [{ "cron": "0 9 * * *", "path": "/api/cron/daily" }] } } ``` ## Top-level keys | Key | Turns on | Details | | --- | --- | --- | | `icon` | The app's icon, a path in the folder | [Publishing](/publishing#an-icon-is-required) | | `data` | Rules for `crow.db` collections | [Data](/data) | | `files` | `crow.files` buckets | [Files](/files) | | `ai` | `crow.ai` | [AI calls](/ai) | | `apis` | `crow.api` with secrets | [Outside APIs and secrets](/apis-and-secrets) | | `live` | `crow.live` channels | [Live updates](/live) | | `server` | Settings for `server/index.js` | [Server code](/server-code) | ## icon ```json { "icon": "assets/logo.svg" } ``` Optional when the folder has `icon.svg` or a `` Crow Nest can find, and wins over both. Needs to be SVG, PNG, ICO or WebP, up to 256 KB. See [Publishing](/publishing#an-icon-is-required) for the rules. ## data One entry per collection. Collections you don't list get `read: "viewers"` and `write: "creator"`. | Key | Values | | --- | --- | | `read` | `public` (anyone who can open the app, signed out too), `viewers` (signed-in people who can open it), `creator` (each person sees their own), `owner` | | `write` | `viewers` (anyone signed in adds and changes anything), `creator` (add; change only your own), `owner`, `public` (signed-out visitors add and change their own entry), `public-add` (signed-out visitors can only add) | | `fields` | Required with `public` or `public-add`. Types: `string`, `string:N` (N up to 10000), `number`, `integer`, `boolean`, `email`, `date`, `datetime`; add `?` for optional | | `unique` | Up to 3 declared fields whose combined value can't repeat | | `editFor` | With `public`: how long a visitor can change their entry, like `30m`, `24h`, `7d` (at most `365d`) | | `dailyLimit` | With `public` or `public-add`: signed-out writes a day for this collection, 1 to 50000 | | `showCreator` | `none` (default), `name` or `email` (not with `read: "public"`) | `read: "public"` together with `write: "public"` is refused; use `public-add` for a public board anyone can add to. ## files One entry per bucket. | Key | Values | | --- | --- | | `read` | `public`, `viewers`, `creator`, `owner` | | `write` | `viewers`, `creator`, `owner`, `public-add` (signed-out uploads on a public app) | | `maxSize` | Largest file, like `10MB` (up to 25 MB, 100 MB for companies) | | `types` | Allowed MIME types, wildcards like `image/*` allowed | ## ai | Key | Values | | --- | --- | | `who` | `owner`, `viewers`, or `public` (public apps only) | | `perViewerPerMinute` | Calls a minute per person | | `dailyLimit` | Calls a day for the whole app | ## apis One entry per API, named by you. | Key | Values | | --- | --- | | `url` | Base URL, `https` and ending in `/` | | `headers` | Headers to send; `{{NAME}}` is replaced with the secret `NAME` | | `query` | Query parameters to add; `{{NAME}}` works here too | | `who` | `owner`, `viewers` or `public` | ## live | Key | Values | | --- | --- | | `who` | Who can join channels: `viewers` (default) or `public` (public apps only) | ## server Only used when the folder has `server/index.js`. | Key | Values | | --- | --- | | `who` | `viewers` (default: signed in and allowed to open the app) or `public` (anyone; public apps only) | | `publicPaths` | Up to 20 paths under `/api/` that skip the access check, for webhooks. A path ending in `*` matches everything under it | | `schedules` | Up to 5 jobs, each `{ "cron": "...", "path": "/api/..." }`. 5-field cron in UTC; personal apps at most every 15 minutes | --- # Data with crow.db `crow.db` gives every app its own database of JSON documents, used straight from the page. There's no schema to set up and no keys: create a collection by adding to it, and set who can read and write in [crow.json](/crow-json). ## Load the library ```html ``` It's served by the app's own address, so it always matches. It defines `window.crow`; every call returns a Promise. ## Who is using the app ```js const me = await crow.me(); // { id, email, name } for a signed-in viewer, or null when signed out if (!me) crow.signIn(); // sends them to sign in and back to this page ``` ## Add, read, change, remove ```js const tasks = crow.db.collection("tasks"); const doc = await tasks.add({ title: "Ship it", done: false }); // the saved document with id, createdBy, createdAt, updatedAt const one = await tasks.get(doc.id); // the document, or null await tasks.update(doc.id, { done: true }); // merges the fields you pass await tasks.remove(doc.id); ``` Documents are up to 64 KB each. Crow Nest sets four fields you can read but not change: `id`, `createdBy`, `createdAt` and `updatedAt` (epoch milliseconds). ## Query ```js const page = await tasks.list({ where: [["done", "=", false], ["priority", ">=", 2]], // up to 4 filters: = != < <= > >= order: "-createdAt", // one field; "-" for descending limit: 50, // 1 to 200, default 50 }); // page = { docs: [...], cursor }. Pass { ...the same query, cursor: page.cursor } for the next page; cursor is null at the end. ``` ## Watch for changes ```js const stop = tasks.watch({ order: "-createdAt" }, (page, err) => { if (err) return console.error(err); render(page.docs); }); // stop() ends it. ``` `watch` updates live when people change the collection, and falls back to checking every 5 seconds when live updates aren't available. ## Who can read and write Set rules per collection in `crow.json`. Collections you don't list default to `read: "viewers"` and `write: "creator"`. ```json { "data": { "tasks": { "read": "viewers", "write": "creator" }, "settings": { "read": "viewers", "write": "owner" } } } ``` | `read` | Who sees documents | | --- | --- | | `public` | Anyone who can open the app, signed out too | | `viewers` | Signed-in people who can open the app | | `creator` | Each person sees only their own | | `owner` | Only the app's owner | | `write` | Who adds and changes | | --- | --- | | `viewers` | Anyone signed in adds and changes anything | | `creator` | Anyone signed in adds; people change only their own | | `owner` | Only the app's owner | | `public` | Signed-out visitors add, and change their own entry | | `public-add` | Signed-out visitors can only add | ### Finding "mine" By default other readers see only an author's id: `createdBy` is `{ id }`. Compare it with the viewer: ```js const me = await crow.me(); const mine = page.docs.filter((d) => d.createdBy.id === me.id); ``` To show more, set `showCreator` to `name` or `email` on the collection (not allowed with `read: "public"`). ## Entries from signed-out visitors On a `public` app, a collection can take entries from people who aren't signed in: a booking page, a contact form, a game's high scores. ```json { "data": { "rsvps": { "read": "owner", "write": "public", "fields": { "name": "string:80", "email": "email?", "slot": "string:40" }, "unique": ["slot"], "editFor": "7d" }, "scores": { "read": "public", "write": "public-add", "fields": { "name": "string:30", "score": "integer" }, "dailyLimit": 2000 } } } ``` - `fields` is required: entries must match it exactly. Types are `string`, `string:N`, `number`, `integer`, `boolean`, `email`, `date`, `datetime`, with `?` for optional. - `unique` stops two entries sharing the same values, like one booking per slot. A clash answers `conflict`. - With `public`, a visitor can change their own entry later from the same browser, for as long as `editFor` allows. `collection.editLink(id)` gives a link that lets them do it from another device. - The library runs the bot check itself. Signed-out visitors can add 5 entries a minute and 50 a day each, and `dailyLimit` caps the collection. - The owner can block a visitor's entries from the dashboard's Data tab. ## Errors Calls throw an `Error` with a `code`: `sign_in_required` (a change by a signed-out visitor sends them to sign in), `forbidden`, `not_found`, `bad_request`, `too_large`, `limit_reached`, `rate_limited`, `conflict`, `edit_closed` or `payment_required`. See [Error codes](/errors). ## Limits 60 writes a minute per person; 100 MB of data per app (1 GB for companies). See [Limits](/limits). ## Seeing what's saved The dashboard's Data tab shows each collection with its count, rules and recent documents, and exports everything as JSON. Your AI can use `tool_data` for the same view. --- # File uploads `crow.files` stores uploads in buckets you declare in [crow.json](/crow-json). Each bucket has its own rules, size limit and allowed types. ## Declare a bucket ```json { "files": { "receipts": { "read": "creator", "write": "viewers", "maxSize": "10MB", "types": ["image/*", "application/pdf"] } } } ``` | Key | Values | | --- | --- | | `read` | `public`, `viewers`, `creator`, `owner` | | `write` | `viewers`, `creator`, `owner`, `public-add` | | `maxSize` | Largest file, like `10MB` | | `types` | Allowed MIME types; wildcards like `image/*` work | ## Upload and use files ```js const receipts = crow.files.bucket("receipts"); const f = await receipts.upload(input.files[0]); // { id, name, size, type, createdBy, createdAt, url } img.src = receipts.url(f.id); // use it in , or fetch() const { files, cursor } = await receipts.list({ limit: 50, mine: true }); await receipts.get(f.id); // the record, or null await receipts.remove(f.id); ``` ## Uploads from signed-out visitors On a `public` app, `write: "public-add"` lets signed-out visitors upload files up to 5 MB each. ## Safety HTML and SVG uploads are always served as downloads, never as pages, so an upload can't run code on your app's address. ## Limits Files are up to 25 MB each (100 MB for companies). Storage counts toward your plan: see [Plans and billing](/pricing). The dashboard's Data tab lists every bucket's files with their sizes and a delete button. --- # Live updates There are two kinds of live updates: - **Saved data that updates for everyone.** Use `watch` on a collection: when anyone adds or changes a document, every open page gets the new list. See [Data](/data#watch-for-changes). - **Messages that aren't saved.** Cursors, typing indicators, game moves. Use a `crow.live` channel. ## Turn on channels ```json { "live": { "who": "viewers" } } ``` `who` is `viewers` (signed-in people who can open the app, the default) or `public` (signed-out visitors too, public apps only). Without a `live` section, `watch` still updates live for anyone who can read the collection; only channels need it. ## Use a channel ```js const room = crow.live.channel("board"); room.on("message", (data, from) => move(data)); // from = { userId, visitor } room.on("presence", (people) => showWho(people)); // who is in the channel now room.send({ x: 10, y: 20 }); // any JSON, up to 4 KB room.leave(); ``` Everyone in the same app who joins a channel with the same name gets each message. Messages aren't stored: someone who joins later doesn't see earlier ones. Save anything that should last with [crow.db](/data). --- # AI calls `crow.ai` lets a page call an AI model without putting a key in the page. By default it uses **Crow Nest AI**: nothing to set up, no key to find. You can also bring your own key. ## Crow Nest AI An app that turns AI on in `crow.json` works as soon as it's published. Crow Nest makes the call for you and adds what it used to your monthly bill, at cost plus 15%. The first $1 each month is included. - **Who can use it.** A personal workspace needs a paid plan with a card (Flat or Pay as you go). An organization or company needs billing set up. Without that, calls answer `ai_needs_billing` and the app's owner is told to add a card. - **The cap.** Each workspace has a monthly cap on AI spend, $25 unless you change it (Settings, or the organization's Billing page; 0 turns Crow Nest AI off). You get an email at 80% and at 100%. At 100%, calls answer `ai_cap_reached` until next month or a higher cap. - **Models.** `anthropic/claude-haiku-5.5` (fast, the default), `anthropic/claude-sonnet-5.5` (balanced) and `anthropic/claude-opus-5.5` (strongest). The app's AI page lists them too. Pass one of their ids as `model`; any other id answers a 400 that names the allowed ones. - **Organizations.** Admins can switch Crow Nest AI off for everyone in the Policy page. - **Privacy.** Prompts and replies go to the model's maker and are never stored by Crow Nest. We keep counts and costs only. Apps published before Crow Nest AI that had AI on but no key switch to it on their next publish, and the publish reply says so. ## Bring your own key If you'd rather use your own account, add a key. Then the provider bills that key and Crow Nest adds nothing. ## 1. Add a key Ask your AI to add an AI key. It uses the `ai_key` tool, which asks you for the key privately; never paste a key into the chat. You can also add one in the dashboard under Settings, AI keys. Crow Nest works out the provider from the key: Anthropic, OpenAI, Google, or an OpenAI-compatible service. | Key scope | Belongs to | Who adds it | | --- | --- | --- | | Personal | You; used by apps you own | You | | Company | Your organization or company | Admins | | App | One app | The app's owner | Keys are stored encrypted on the server and never shown again. ## 2. Choose which key the app uses Ask your AI to set it (the `tool_ai` tool) or use the app's AI page in the dashboard: `crow` (Crow Nest AI, the default), `company`, `mine` (your personal key), `tool` (a key saved on the app) or `none`. > **Important** Using your own key on an app other people can open means their calls are billed to you. Crow Nest asks you to accept that warning first, and shows the same warning when you later share the app more widely. ## 3. Turn it on in crow.json ```json { "ai": { "who": "viewers", "perViewerPerMinute": 10, "dailyLimit": 500 } } ``` `who` is `owner`, `viewers`, or `public` (public apps only). `perViewerPerMinute` and `dailyLimit` cap use. ## 4. Call it ```js const r = await crow.ai.chat({ messages: [{ role: "user", content: "Summarize this: ..." }], system: "You are concise.", // optional model: "claude-sonnet-5-5", // optional; the key's default model otherwise maxTokens: 1024, // optional, at most 8192 }); r.text; // the answer r.usage; // { inputTokens, outputTokens } try { for await (const chunk of crow.ai.stream({ messages })) out.textContent += chunk; // streamed text } catch (e) { // e.code "stream_interrupted": the answer was cut off part way. Keep what arrived and offer a Retry. } ``` ## Errors `ai_not_enabled` (no `ai` in crow.json), `ai_no_key` (no key for this app), `ai_key_needs_ack` (the owner hasn't accepted the cost warning), `ai_needs_billing` (Crow Nest AI without a card or billing), `ai_cap_reached` (the monthly cap), `rate_limited`. A stream can also stop part way: `stream_interrupted` when the connection drops, or `ai_provider_error` when the model's provider cuts the answer off. The pieces that arrived stay; the loop throws instead of ending, so an app can tell a cut-off answer from a finished one. ## Seeing costs Settings and the organization's Billing page show what Crow Nest AI cost this month, what's included and the cap. The app's page in the dashboard shows AI calls, tokens and estimated cost per key. Company admins see the same for company keys. ## From server code Server code calls the same models with `env.CROW.ai.chat(...)`, under the same key setting and limits. See [Server code](/server-code). --- # Outside APIs and secrets Many apps need a service that takes a secret key: Stripe, Airtable, a weather API. Putting the key in the page would hand it to everyone who opens the app, so Crow Nest keeps it on the server and adds it to each call for you. ## 1. Declare the API in crow.json ```json { "apis": { "stripe": { "url": "https://api.stripe.com/v1/", "headers": { "authorization": "Bearer {{STRIPE_KEY}}" }, "who": "viewers" } } } ``` - `url` must be `https` and end in `/`. Calls can only reach paths under it. - `{{NAME}}` placeholders work in `headers` and `query` and are filled from the app's secrets. - `who` is `owner`, `viewers` or `public`. ## 2. Set the secret Ask your AI to set it. The `tool_secret` tool asks you for the value privately: it never passes through the conversation. You can also set secrets on the app's Secrets page in the dashboard. An app can have up to 20 secrets, each up to 8 KB, and values are encrypted at rest. ## 3. Call it ```js const res = await crow.api("stripe").fetch("customers?limit=3"); // a normal fetch Response const data = await res.json(); ``` The call goes through Crow Nest, which checks who's calling, adds the secret, and forwards it. The browser never sees the key. ## From server code Server code gets the same APIs with `env.CROW.api("stripe").fetch(...)`, and every secret on `env` by name (`env.STRIPE_KEY`). See [Server code](/server-code). > **Warning** Never put a key in the app's files. Publishing refuses files that contain API keys or tokens. --- # Server code Most apps need nothing but the browser library. When they do need a server (a webhook from Stripe, logic that must not run in the browser, joining data from several APIs, or SQL), add `server/index.js` to the published folder. Requests to `/api/*` on the app's address run it, isolated from every other app. ## Your first function ```js // server/index.js export default { async fetch(request, env) { const me = env.CROW.viewer; // { userId, email, name }, or null return Response.json({ hello: me ? me.name : "visitor" }); }, }; ``` Publish the folder. `https://.crow-nest.com/api/hello` now answers from your code, and `/server/index.js` is never served to browsers. Requests go through the same access check as the pages, so a private app's API is private too. ## What your code gets | On `env` | What it is | | --- | --- | | `env.CROW.viewer` | The signed-in person (`{ userId, email, name }`) or `null`. Set by Crow Nest; a request can't fake it | | `env.CROW.db` | The app's [crow.db](/data) collections, with the owner's rights | | `env.CROW.sql(query, ...bindings)` | The app's own SQLite database | | `env.CROW.api(name)` | An API declared in `crow.json`, with its secrets filled in | | `env.CROW.ai.chat(...)` | An AI call, using the app's key setting | | `env.CROW.later(path, body, options)` | Queue a background task | | `env.` | Each of the app's [secrets](/apis-and-secrets), by name | ```js export default { async fetch(request, env) { const me = env.CROW.viewer; await env.CROW.sql("CREATE TABLE IF NOT EXISTS orders (id INTEGER PRIMARY KEY, email TEXT)"); const rows = await env.CROW.sql("SELECT * FROM orders WHERE email = ?", me.email); const notes = await env.CROW.db.collection("notes").list({ limit: 10 }); const res = await env.CROW.api("stripe").fetch("customers?limit=3"); const answer = await env.CROW.ai.chat({ messages: [{ role: "user", content: "Hi" }] }); return Response.json({ rows, notes: notes.docs }); }, }; ``` ## SQL `env.CROW.sql` is a SQLite database that belongs to the app's server code: up to 100 MB, up to 1,000 rows per query (add a `LIMIT`). It's separate from crow.db, which the browser uses; neither can see the other's tables, and no app can reach another app's data. Use `?` placeholders for values. ## Who can call it Set this in `crow.json`: ```json { "server": { "who": "viewers", "publicPaths": ["/api/webhooks/stripe"] } } ``` - `who: "viewers"` (default): signed-in people allowed to open the app. `who: "public"`: anyone (public apps only). - `publicPaths`: paths that skip the access check, even on a private app. Use them for webhooks from outside services, and check the webhook's signature yourself. A path ending in `*` matches everything under it. ## Scheduled jobs Run code on a timer with `server.schedules`: ```json { "server": { "schedules": [{ "cron": "0 9 * * *", "path": "/api/cron/daily" }] } } ``` - Cron has 5 fields (minute, hour, day of month, month, day of week) in UTC, with lists, ranges, steps and `*`. - Up to 5 jobs. Personal apps run at most every 15 minutes; companies every minute. - Each run sends a `POST` to your path with no signed-in user and the header `x-crow-schedule` set to that job's cron. Crow Nest removes `x-crow-*` headers from every outside request, so the header proves the call is a scheduled run. - A suspended or paused app's jobs don't run. A failed run isn't retried; it shows in the logs. ```js const url = new URL(request.url); if (url.pathname === "/api/cron/daily") { if (!request.headers.get("x-crow-schedule")) return new Response("Not found", { status: 404 }); await sendDailySummary(env); return Response.json({ ok: true }); } ``` ## Background tasks Do work after responding, or later: ```js await env.CROW.later("/api/send-email", { to: "sam@example.com" }, { delaySeconds: 60 }); ``` This queues a `POST` to your own path with the body as JSON (up to 64 KB), after 0 to 86,400 seconds. The call carries the header `x-crow-task`. If your code answers with a 5xx or throws, it's retried up to 3 times, then dropped and logged. Personal apps can queue 1,000 tasks a day and companies 100,000; after that `later` throws `limit_reached`. ## Logs `console.log`, `console.warn`, `console.error` and uncaught errors are kept: the newest 500 lines per app, for 7 days, each with the time, level and the request path or job. Any value of one of your app's secrets is shown as `[secret]`. Read them on the app's page in the dashboard (Server code, with a level filter), or ask your AI, which uses `tool_logs` (the last 100 lines). Only the app's owner can read them. ## Outside requests `fetch()` to the public internet works. Crow Nest's own addresses, IP addresses, `localhost` and internal names are blocked; use `env.CROW` for the app's own data, AI and APIs. ## Packages Server code is uploaded as is; Crow Nest doesn't run `npm install`. If you use npm packages, bundle them into `server/` first (esbuild or your build tool). Relative imports between files in `server/` work, and so do `node:` built-ins. Bare imports like `import Stripe from "stripe"` are refused at publish with "Bundle your server code first". Up to 10 MB of server code. ## Limits | | Personal | Companies and organizations | | --- | --- | --- | | CPU per request | 50 ms | 500 ms | | Outside requests per request | 50 | 200 | | Schedules | Every 15 minutes at most | Every minute | | Background tasks a day | 1,000 | 100,000 | A request over its limit answers `503` with a message saying so; other apps are never affected. Function requests count toward Compute, billed at $2 per million beyond your plan's allowance (see [Plans and billing](/pricing)). See [Plans and billing](/pricing). ## Seeing what happens The app's page in the dashboard shows server code requests, errors and average CPU time for the last 7 days, and the log lines. --- # MCP server and tools The MCP server is how your AI works with Crow Nest. It's at: ```text https://crow-nest.com/mcp ``` It speaks MCP over streamable HTTP. The first connection opens a browser window to sign in to Crow Nest and approve the client; after that, the AI acts as you. Revoke access any time from the dashboard's Tokens page. ## Connecting your AI Paste `https://crow-nest.com/mcp` into your AI and it either connects itself or tells you the steps below. No client ID or API key is needed in any app. **Claude.ai or Claude desktop** (it then works in the Claude phone app too) 1. Open **Customize > Connectors**. 2. Click **Add**, then **Add custom connector**. 3. Name it `Crow Nest`, paste `https://crow-nest.com/mcp` as the URL, and click **Add**. Leave the sign-in options as they are. 4. Click **Connect** and sign in to Crow Nest. The Free plan allows one custom connector. On Team or Enterprise, an owner adds it once under **Organization settings > Connectors**, then each person clicks **Connect**. **ChatGPT** (paid plans, set up on chatgpt.com) 1. Open **Settings > Apps & Connectors > Advanced settings** and turn on **Developer mode**. 2. Back in **Apps & Connectors**, click **Create**. 3. Name it `Crow Nest`, paste `https://crow-nest.com/mcp`, choose **OAuth**, confirm you trust it, and click **Create**. 4. Sign in to Crow Nest. In a chat, pick Crow Nest from the **+** menu. **Claude Code** (terminal, IDE, or the Code tab in Claude desktop) ```bash claude mcp add --transport http -s user crow-nest https://crow-nest.com/mcp ``` Then type `/mcp`, pick crow-nest, and choose **Authenticate**. **Codex**: run these in your terminal, then start a new session. ```bash codex mcp add crow-nest --url https://crow-nest.com/mcp codex mcp login crow-nest ``` **Cursor and VS Code**: open [crow-nest.com/mcp](https://crow-nest.com/mcp) in your browser and click **Add to Cursor** or **Add to VS Code**. For VS Code you can also run: ```bash code --add-mcp '{"name":"crow-nest","type":"http","url":"https://crow-nest.com/mcp"}' ``` **Windsurf**: add this to `~/.codeium/windsurf/mcp_config.json`, then refresh the MCP servers panel. ```json { "mcpServers": { "crow-nest": { "serverUrl": "https://crow-nest.com/mcp" } } } ``` **Gemini CLI** ```bash gemini mcp add --transport http crow-nest https://crow-nest.com/mcp ``` Then type `/mcp auth crow-nest`. **Anything else**: add a remote MCP server at `https://crow-nest.com/mcp` (Streamable HTTP, OAuth). **Keep the rules in the project (optional).** Crow Nest's own instructions already tell your AI how to publish. To also keep them in a project, so every session follows them even before it calls Crow Nest, add them to the file your AI reads from the project's root folder: - Codex, and most other agents (AGENTS.md): `curl -s https://crow-nest.com/mcp/rules >> AGENTS.md` - Claude Code (CLAUDE.md): `curl -s https://crow-nest.com/mcp/rules >> CLAUDE.md` - Cursor (a project rule that is always on): `mkdir -p .cursor/rules && curl -s "https://crow-nest.com/mcp/rules?for=cursor" -o .cursor/rules/crow-nest.mdc` ## How tools ask you things Some actions need your answer: who should see a new app, confirming a purchase or a delete. When your AI client supports it, the tool asks you directly in a form the AI can't read or fill in. When it doesn't, the tool tells the AI to ask you and call again, or gives you a dashboard link. Secret values, AI keys and card details never pass through the conversation or the MCP server: the tools give you a link to the dashboard or Stripe, and you type them there. Every tool tells your AI whether it only reads, changes something, or can delete something (MCP tool hints), so clients that ask before changes can do so. ## Publishing and apps | Tool | What it does | | --- | --- | | `publish` | Publishes a built folder: creates an app (asks for a name and who can see it) or a new version of one | | `crow_docs` | Returns the developer reference: the browser library, crow.json rules and server code | | `list_tools` | Your apps with their address, visibility and opens in the last 7 days, plus apps shared with you | | `whoami` | Which account the connection is signed in as: email, name, and personal account or company | | `share` | Changes who can open an app; adds or removes people by email; turns `aiDiscoverable` on for public apps | | `rollback` | Makes an older version current again | | `delete_tool` | Deletes an app after you confirm. Its address can't be reused | ## Data, logs and stats | Tool | What it does | | --- | --- | | `tool_data` | Each crow.db collection with its count, rules and recent documents, and each file bucket's count | | `tool_logs` | The last 100 log lines from the app's server code, with a level filter. Owner only | | `tool_stats` | Opens and unique viewers over the last few days | ## Secrets and AI | Tool | What it does | | --- | --- | | `tool_secret` | Gives you the dashboard link to set or replace a secret an app's `apis` refers to; deletes one. The value never goes through your AI | | `ai_key` | Gives you the dashboard page to add an AI provider key (your own, your company's or one app's); deletes one. The key never goes through your AI | | `tool_ai` | Chooses which key an app uses: `company`, `mine`, `tool` or `none`, with the cost warning when needed | ## Domains | Tool | What it does | | --- | --- | | `company_domain` | Shows the company's own address for its apps and the DNS records still needed | | `company_domain_set` | Company admins set up or remove that address | | `custom_domains` | Lists an app's own addresses and how far along each is | | `custom_domain` | Puts an app on your own subdomain and lists the DNS records to add; disconnects one | | `find_domain` | Finds domains to buy, with first-year and renewal prices | | `buy_domain` | Buys a domain you picked, after you confirm the price and the owner's details | | `domain_status` | Where a bought domain is: registering, DNS, certificate, live, renewal date | ## Billing | Tool | What it does | | --- | --- | | `billing` | Your plan, state, whether a card is on file and this month's usage, with a Stripe link when you need to act. Card details are only ever typed on Stripe | ## Organizations | Tool | What it does | | --- | --- | | `org` | Shows your organization and what you may do | | `org_setup` | Creates an organization, turns your account into one, or accepts an invitation | | `org_invite` | Invites people by email (up to 50 at once) with a role and permissions, or revokes an invitation | | `org_members` | Lists members and invitations | | `org_member_update` | Managers change roles and permissions or remove people (choosing who gets their apps, after a list of what will move); the owner transfers ownership | | `org_apps` | Lists every app in the organization | | `org_app_pause` | Managers pause or unpause an app | | `org_activity` | Managers read the activity log, newest first (latest 50), filtered by date, person, app or action | ## The reference for agents `crow_docs` returns the same developer reference that's at `https://crow-nest.com/mcp/docs`. These docs go further; agents can read any page as Markdown (see [For AI agents](/for-ai-agents)). ## In ChatGPT ChatGPT's app rules don't allow buying digital services inside the chat, so there `find_domain` and `buy_domain` aren't offered and `billing` links to Settings on crow-nest.com instead of a Stripe page. Your plan, card and domains work the same; you manage them in the dashboard. Every other tool is the same in every client. --- # CLI `crow` is a thin client for the same API the MCP server and the dashboard use. It's published to npm as `crow-nest` and needs Node 20 or newer. ```bash npx crow-nest login # run it without installing npm i -g crow-nest # or install it; the command is then `crow` ``` `crow --version` prints the version. About once a day, after a command finishes, the CLI checks npm and prints one line if a newer version exists. It stays quiet with `--json`, when `CI` is set, or when `CROW_NO_UPDATE_CHECK=1`. Set `CROW_API` to point the CLI at another API, for staging or local development (the default is `https://api.crow-nest.com`). ## Commands | Command | What it does | | --- | --- | | `crow login` | Signs you in: prints a code and opens `crow-nest.com/device` to approve it. The token is kept in your system's keychain | | `crow publish [dir]` | Builds and publishes the project in `dir` (default: the current folder). The first publish asks for a name and visibility and writes `.crow.json` with the app's ID | | `crow share [--public \| --link \| --org \| --private] [--add email] [--remove email]` | Changes who can open an app | | `crow ls` | Lists your apps | | `crow open ` | Opens the app's address in your browser | | `crow whoami` | Prints which account you are signed in as (name, email, and company if you are in one) | | `crow logout` | Removes the stored token | ## Building `crow publish` works with projects, not just folders. It recognizes Vite, React, Next.js, Astro, SvelteKit, Nuxt and Remix, runs the project's build on your machine, checks the output will work on Crow Nest, and publishes it. Nothing is ever built on Crow Nest's servers. A plain folder of web files is published as is. ## .crow.json After the first publish, `.crow.json` in your project holds the app's ID, so later `crow publish` runs update the same app. Commit it if your team publishes the same app; delete it to publish as a new app. --- # Limits Limits keep one app from affecting others and keep costs predictable. Where they differ, organizations and companies get the larger number. ## Publishing | | Limit | | --- | --- | | App size, unzipped | 300 MB | | Files per app | 25,000 | | One CLI upload, zipped | 100 MB | | People an app is shared with | 200 | | Server code | 10 MB | ## Data (crow.db) | | Personal | Organizations and companies | | --- | --- | --- | | Data per app | 100 MB | 1 GB | | Document size | 64 KB | 64 KB | | Writes per person | 60 a minute | 60 a minute | | Signed-out writes per visitor | 5 a minute, 50 a day | 5 a minute, 50 a day | | Filters per query | 4 | 4 | | Documents per page | 200 | 200 | | `dailyLimit` for signed-out writes | 1 to 50,000 | 1 to 50,000 | ## Files | | Personal | Organizations and companies | | --- | --- | --- | | File size | 25 MB | 100 MB | | Signed-out upload | 5 MB | 5 MB | ## Live updates | | Limit | | --- | --- | | Channel message | 4 KB | ## AI | | Limit | | --- | --- | | `maxTokens` per call | 8,192 | | Calls | Set per app with `perViewerPerMinute` and `dailyLimit` | ## Secrets | | Limit | | --- | --- | | Secrets per app | 20 | | Secret size | 8 KB | ## Server code | | Personal | Organizations and companies | | --- | --- | --- | | CPU per request | 50 ms | 500 ms | | Outside requests per request | 50 | 200 | | SQL database | 100 MB | 100 MB | | Rows per SQL query | 1,000 | 1,000 | | Scheduled jobs | 5, every 15 minutes at most | 5, every minute at most | | Background tasks | 1,000 a day | 100,000 a day | | Background task body | 64 KB | 64 KB | | Background task delay | Up to 24 hours | Up to 24 hours | | Log lines kept | Newest 500, for 7 days | Newest 500, for 7 days | When a limit is reached, calls answer with `limit_reached`, `rate_limited` or `too_large` and a message saying which limit. See [Error codes](/errors). --- # Error codes Errors from the browser library are thrown as an `Error` with a `code`. The API and server code calls answer with JSON in the same shape: ```json { "error": { "code": "forbidden", "message": "Only the app's owner can change this." } } ``` The `message` is written to be shown to a person as it is. ## Data, files and APIs | Code | Meaning | What to do | | --- | --- | --- | | `sign_in_required` | A signed-out visitor tried something that needs sign-in | The library sends them to sign in automatically | | `forbidden` | The rules don't allow this person to do this | Check `read` and `write` in [crow.json](/crow-json) | | `not_found` | No such document, file or app | Check the ID | | `bad_request` | The request doesn't match what's expected, such as a field type | Fix the input; the message says what's wrong | | `too_large` | A document, file, body or query result is over its limit | See [Limits](/limits) | | `limit_reached` | A quota is used up: data size, daily writes, tasks | Wait for the next day, or delete data | | `rate_limited` | Too many calls in a short time | Slow down and retry | | `conflict` | A `unique` value is already taken | Ask for a different value, like another slot | | `edit_closed` | The `editFor` window for a visitor's entry has passed | The entry can't be changed any more | | `payment_required` | Paid usage has grown with no card for 14 days | The owner adds a card in Settings | ## AI | Code | Meaning | | --- | --- | | `ai_not_enabled` | The app's crow.json has no `ai` section | | `ai_no_key` | No AI key is set up for this app | | `ai_key_needs_ack` | The owner hasn't accepted the warning for using their own key on a shared app | ## Publishing and billing | Code | Meaning | | --- | --- | | `visibility_required` | A new app needs a visibility; ask the person | | `card_required` | This account has no card on file yet (new accounts add one at sign-up); the message links to Settings | | `owner_only` | Only the organization's owner can do this, such as change billing | | `functions_unavailable` | Server code can't be published right now | ## Server code | Status | Code | Meaning | | --- | --- | --- | | 503 | `function_over_limit` | The request ran past its CPU or outside-request limit | | 503 | `function_error` | The code failed to answer | | 403 | `blocked_address` | `fetch()` to a Crow Nest address, an IP address or an internal name | ## App status | Status | Meaning | | --- | --- | | 403 | The viewer can't open this app; the page offers Request access | | 423 | The organization paused this app | | 451 | Crow Nest suspended this app (see [Security and trust](/security)) | ## Still stuck? Email [support@crow-nest.com](mailto:support@crow-nest.com) with the error code, the app's address and what you were doing. If Crow Nest itself failed (an internal error), the answer and the dashboard show a reference; include it and we can find the exact error. --- # Organizations and companies Apps can live in a shared workspace instead of one person's account. There are two kinds: - **Organizations** are made by anyone, at sign-up or later, and people join by invitation from any email domain, Gmail included. - **Companies** are set up by Crow Nest for a business, behind the company's own login. Everyone who signs in with the company's email domain joins automatically. Both get a shared list of every app, `org` visibility (everyone inside can open it), policies, and shared AI keys. ## Make an organization - At sign-up, choose **Create an organization** and give it a name. - Later, ask your AI (the `org` tool) or use Settings, **Turn my account into an organization**. Everything moves with you: every app keeps its address, data, versions and stats; your AI keys become organization keys; and your plan, card and bought domains come along. ## Roles and permissions | Role | Can | | --- | --- | | Member | Use the organization's apps, and publish and share within the switches below | | Manager | Everything a member can, plus invite and remove people, change their permissions, set policies, pause apps | | Owner | Everything a manager can, plus make and remove managers, rename, change billing, transfer ownership and delete the organization. There's exactly one | Each member also has two switches, set by a manager: - **Can publish**: whether they can publish apps into the organization. - **Can share outside**: whether they can share apps with people outside it. ## Inviting people Managers invite by email (up to 50 at once) from the Organization tab or with the `org_invite` tool, choosing the role and the two switches. The person gets an email with a link; only they, signed in with that address, can accept it. ## The shared app list The Organization tab lists every app with its owner, visibility and opens this week. Managers can **pause** an app: viewers see a page saying the organization paused it, until a manager unpauses it. ## Company domain Owners and managers can put every app on the organization's own domain, like `expenses.apps.acme.com`, by setting it once under **Company domain**. See [Custom domains](/custom-domains#company-domains). ## Policies Managers and company admins decide whether apps may be public, whether they may be shared by link, and whether they may be shared with emails outside the organization. See [Sharing and access](/sharing#company-and-organization-policies). ## Billing - **Organizations** are paid for by the owner on the personal plans ($7 Crow Nest Personal, or Pay as you go). Everyone else sees the plan and usage, and anyone who needs a card is told to ask the owner. - **Companies** pay $5 per person per month. Each person adds an allowance to a pool the company shares, and the company's admins set up and manage billing. See [Plans and billing](/pricing). ## Companies When Crow Nest sets up a company, it gets: - its own login: people sign in with their work email and land in the company workspace; - automatic membership for everyone on the company's email domains; - apps people already published on a personal account with that domain move into the company, keeping their addresses; - company admins, who see every app and its usage, set policies, manage AI keys and billing. To set up a company, [get in touch](https://www.crow-nest.com/contact/). --- # IT admin This page is for the people who run a company's tools. It applies to companies set up by Crow Nest and to organizations. "Admin" means a company's admins, or an organization's owner and managers. Members don't see any of these pages. ## Add and remove people automatically (SCIM) Crow Nest speaks SCIM 2.0, so Okta and Microsoft Entra can add people when they join your company and turn off their access when they leave. People still sign in with Google or Microsoft. 1. Open **People**, then **Automatic sign-up (SCIM)**, and choose **Create a token**. The token is shown once. Copy it now. 2. Copy the base URL shown on that page. It looks like `https://api.crow-nest.com/scim/v2`. 3. Connect your identity provider: **Okta** - Open your SCIM 2.0 app, then Provisioning and Integration. - Set the base URL, set the unique identifier field to `userName`, and choose HTTP Header authentication with the token. - Turn on Create Users, Update User Attributes and Deactivate Users. **Microsoft Entra** - Go to Enterprise applications, New application, Create your own application, then Provisioning and Automatic. - Set the Tenant URL to the base URL and the Secret token to the token. - Test the connection, then turn provisioning on. What happens: - **A new person** gets an account and a membership as a member. They sign in later with Google or Microsoft as that email and land in your workspace, with no invite step. - **Someone who already uses Crow Nest** with their own apps isn't moved. They get a message to be invited from Crow Nest instead, so they can choose what moves. Someone with an empty personal workspace is moved in. - **A company set up by Crow Nest** only accepts emails from its own domains. - **Turning someone off** (`active: false`, or deleting them) removes them as described below. Turning them back on restores their membership, not their apps. - A person who was turned off and signs in sees "Your company has turned off your Crow Nest access. Ask your IT team." and gets no workspace. - Roles aren't set by SCIM. Everyone joins as a member and admins change roles in Crow Nest. Seats are charged the same as for invited people. Make a new token to replace the old one, or **Revoke** it to stop syncing. Only one token is active at a time. ## When someone leaves Removing a person (**Remove** on People or Members, SCIM, or the person leaving) does all of this at once: - Their apps in the workspace pass to the person you choose, with their shares and settings unchanged. With no choice, they go to the workspace's offboarding person, else the owner. - Their sessions, API tokens and AI tool sign-ins stop working. - Shares of the workspace's apps to their email are removed. - Their personal AI keys are taken off the workspace's apps; those apps use the company key, or AI turns off. - Domains, company AI keys and anything else the workspace owns stay. - They get a new personal workspace. The person who receives the apps gets one email listing them. Before you confirm, **Remove** shows the list of apps with their visibility and how many people each is shared with. Your AI shows the same list with `org_members`. ## Activity log **Activity** lists changes and access decisions for the last 400 days: dashboard sign-ins, publishes, rollbacks, deletes, visibility and share changes, pauses, members added and removed, role and policy changes, SCIM token changes, AI key and secret names (never their values), domain changes, and exports. Opening an app is not logged. See that in stats. Each row shows who (a person, SCIM, Crow Nest staff, or the system), what, which app or person, when, and the IP and browser. Filter by date, person, app and action, then choose **Download CSV** for up to 100,000 rows. The export itself is logged. Your AI can read the latest 50 rows with `org_activity`. ## Limit sharing to partner domains On **Policy**, with sharing outside the company allowed, add **Partner domains**. Outside shares then only work for those domains, matched exactly: `partner.com` doesn't allow `sub.partner.com`. The refusal names the allowed domains. Public and link sharing follow their own switches. Changing the policy never changes apps by itself. The **Apps the policy no longer allows** list shows public apps, link apps and outside shares the policy would refuse now. **Bring these in line** sets those apps to your company, removes the outside shares, logs each change and emails each app's owner once. > **Note** Okta and Entra reach Crow Nest from their own servers, so there is nothing to allow on your network. See also [Organizations and companies](/organizations) and [Sharing](/sharing). --- # Plans and billing Hosting, publishing and opening apps are free. You pay for what apps store and run, either as one flat price or as you go. AI calls use Crow Nest AI at cost plus 15% (the first $1 a month is included), or your own key with no markup. ## Personal plans | | Crow Nest Personal | Pay as you go | | --- | --- | --- | | Price | $7 a month | $0, then usage | | Apps, hosting, publishing | Included | Free | | Opens | 100,000 a month included | First 100,000 a month free, then $5.00 per million | | Database | 1 GB included | $1.00 per GB per month | | File storage | 10 GB included | $0.05 per GB per month | | Compute (server function requests and database calls) | 1 million included | $2.00 per million | | Free usage | | The first $1 each month | | Past what's included | Pay-as-you-go rates | | | AI calls | Crow Nest AI at cost plus 15%, first $1 a month included; or your own key, no markup | Crow Nest AI at cost plus 15%, first $1 a month included; or your own key, no markup | | Domains bought through Crow Nest | Registrar price + $1 a year, plus card fees | Registrar price + $1 a year, plus card fees | Prices are before tax. Tax is added at checkout based on your billing address. ## Companies Companies Crow Nest sets up pay **$5 per person per month**. Each person adds 1 GB of database, 10 GB of files, 1 million compute requests and 100,000 opens to a pool the whole company shares; usage past the pool is billed at the pay-as-you-go rates. Seats follow the company's members automatically. [Get in touch](https://www.crow-nest.com/contact/) to set one up. Organizations someone makes themselves are paid for by their owner on the personal plans. ## Adding a card You add a card when you sign up. After you pick a plan, Stripe's checkout opens: - **Crow Nest Personal** charges $7 today and then every month. - **Pay as you go** charges nothing today and saves the card for usage. If you leave checkout without finishing, the plan screen comes back the next time you sign in, until a card is saved. Accounts made before this, or that left checkout, can add one any time in the dashboard's Settings: pick Crow Nest Personal under Plan and press Save, or press **Add a card** for Pay as you go. Without a card, publishing answers `card_required`. Card details are only ever typed on Stripe's pages; Crow Nest never sees them. ## How usage is billed Every night, Crow Nest sends the previous day's usage to Stripe: storage as the amount kept that day, compute as the requests made, and opens as the times your apps were opened. Stripe adds them up and puts them on your next monthly invoice, as four lines: **Database** and **Files** in GB-months (1 GB kept for a whole month), **Compute** in millions of requests and **Opens** in millions of opens. Only what's past your plan's allowance is sent. **What counts as Compute.** Each server function request counts one. Each `crow.db` call counts one too, or one per row written when a call writes more than one row (a bulk insert of 500 rows counts 500). Settings shows this month's usage and an estimate as it goes. If your apps go past what your plan includes in a month, the first time it happens that month you get an email listing what's over and what it costs so far. On Pay as you go with no card, apps keep working when they start using paid services; you're asked to add a card, and after 14 days without one, writes that would grow paid usage answer `payment_required` while pages, reads and deletes keep working. ## Invoices, cards and cancelling Settings, Billing shows your invoices. **Manage card and cancel** opens Stripe's billing portal, where you can update your card, see every invoice, and cancel. Cancelling keeps your plan until the end of the period you paid for; Settings shows the date. After that you're on the **Viewer** plan, $0: you can open anything shared with you and your apps keep working, but publishing needs a plan again. **Keep my plan** in Settings undoes a cancel before it takes effect. If a payment fails, nothing stops working while Stripe retries. After 14 days without payment the plan ends. Questions about a charge or an invoice? Email [support@crow-nest.com](mailto:support@crow-nest.com). --- # Security and trust ## Sign-in People sign in with Google, Microsoft, or a one-time code sent to their email. Sign-in happens on `crow-nest.com`; each app address gets its own short-lived session through a one-time handoff, so apps never see passwords or long-lived tokens. Sessions are signed cookies scoped to one app's address. ## Access to apps Every request to an app, its files, its data and its server code is checked against the app's visibility and the viewer's identity before anything is returned. Rules for data and files are enforced on the server, never in the browser. Changes to who can see an app reach every viewer within 60 seconds. ## Keys and secrets - API secrets and AI keys are encrypted at rest (AES-256-GCM, a separate key per app) and are never sent to the browser or shown again after they're saved. - Secrets are typed into private prompts, never through an AI conversation. - Publishing refuses app files that contain API keys or tokens. - In server code logs, any value of the app's secrets is replaced with `[secret]`. ## Server code isolation Each app's server code runs in its own isolated Worker on Cloudflare, with CPU and request limits per call. It can only reach its own app's data: SQL runs in a database that belongs to that app alone, and calls back into Crow Nest carry a token tied to the one app. Its outside requests can't reach Crow Nest's own addresses, IP addresses or internal names. Headers starting with `x-crow-` are removed from every outside request, so a caller can't pretend to be signed in, a schedule or a task. ## Uploads HTML and SVG uploads are always served as downloads, never as pages, so an upload can't run code on an app's address. ## Payments Cards are handled by Stripe. Crow Nest never sees or stores card numbers; it keeps only whether a card is on file. Plans change only when Stripe's signed webhooks confirm them. ## Email Some emails are always sent, because they keep your account safe or your apps running: sign-in codes, account changes, invitations, and billing emails about a card to add or a payment that failed. You can turn off the rest (access requests, usage, domain reminders, organization changes, and notices about your apps) in **Settings, Email** in the dashboard. Each of those emails also has an unsubscribe link that turns off just that kind with one click. ## Abuse and reports Anyone can publish public pages on a `crow-nest.com` address, so Crow Nest watches for abuse: - Public apps carry a small "Made with Crow Nest" badge with a **Report** link. - Reports (phishing, malware, illegal content, abuse) go to Crow Nest staff straight away. - Names that imitate well-known brands are refused, for app addresses and custom domains. - Staff can suspend an app or an account. A suspended app answers 451 and its server code and schedules stop. To report something, use the Report link on the app, or email support@crow-nest.com. ## Your data You can export an app's data from the dashboard at any time. Deleting an app or your account stops it working right away; Crow Nest keeps the data for 30 days in case you change your mind, then deletes it. Domains you bought stay yours.