# formdrop > Multi-tenant schemaless JSON docstore on Cloudflare Workers + D1. Stores arbitrary > JSON documents in named collections, and collects website form submissions without > the site needing a backend of its own. If you are an AI coding agent that was pointed at this site to wire a project up to formdrop: everything you need is below or linked from it, over plain HTTP. You do not need access to the formdrop source repository. API base https://formdrop.abhishek93t.workers.dev Dashboard https://formdrop-dashboard.pages.dev ## Read these four rules before writing any code 1. **Never put an `sk_` key in browser code.** Not in a bundle, not in `NEXT_PUBLIC_*` / `VITE_*` / `PUBLIC_*`. It grants full read/write over every collection in the tenant and cannot be revoked without hand-editing the database. For anything that runs in a browser, use a public ingest URL (`/p/in...`) — the URL itself is the capability, and that is what it is for. 2. **Do not call `POST /signup` yourself.** It creates a new billing tenant and returns a key exactly once, unrecoverable and unrevocable. Ask the human for `FORMDROP_URL` and `FORMDROP_KEY` instead. 3. **`allowed_origins` is a one-shot decision.** It defaults to `*` (any site on the internet may post into the collection) and there is no endpoint to change it later. Set it when you create the collection. 4. **A `200 {"ok":true}` from an ingest URL is not proof of success.** The spam honeypot returns exactly that and silently discards the submission. Confirm the document actually appears in `GET /v1/c/:name` before reporting success. ## Documents - [Full integration guide](https://formdrop-dashboard.pages.dev/integrate.md): self-contained instructions — choosing an access path, creating collections, the paste-ready HTML form with its honeypot, MCP setup, the complete HTTP API reference, the constraints that cause silent failures, a verification script to run before reporting success, and an error-symptom table. **Read this one.** - [MCP server](https://formdrop-dashboard.pages.dev/mcp-server.js): a single dependency-free CommonJS file (Node >= 18) speaking JSON-RPC over stdio. Download it into the project and register it — see below. ## Fastest path: attach a form to a website # 1. create a collection with a public ingest URL (needs the human's key) curl -s -X POST https://formdrop.abhishek93t.workers.dev/v1/collections \ -H "Authorization: Bearer $FORMDROP_KEY" \ -H 'Content-Type: application/json' \ -d '{"name":"contact","public_ingest":true,"allowed_origins":"https://theirsite.com"}' # 2. response gives a RELATIVE ingest_url — prepend the API base # {"id":"c_...","name":"contact","ingest_url":"/p/in<32 hex>","read_url":null} # 3. the browser posts JSON straight to that absolute URL, no auth, no backend # 4. read submissions back with the key curl -s https://formdrop.abhishek93t.workers.dev/v1/c/contact \ -H "Authorization: Bearer $FORMDROP_KEY" The form markup matters — it carries a `_hp` honeypot field that must stay present and hidden, and must not be renamed. Copy it from the integration guide rather than writing your own. ## MCP setup (optional; the HTTP API above is fully sufficient without it) curl -o formdrop-mcp.js https://formdrop-dashboard.pages.dev/mcp-server.js claude mcp add formdrop \ --env FORMDROP_URL=https://formdrop.abhishek93t.workers.dev \ --env FORMDROP_KEY=sk_... \ -- node /absolute/path/to/formdrop-mcp.js The same shape works in `.mcp.json` for Cursor and Claude Desktop. The path must be absolute. Tools: `list_collections`, `create_collection`, `get_form_snippet`, `list_documents`, `get_document`, `upsert_document`. Document deletion and signup are deliberately absent — do those by hand. ## Gotchas that waste the most time - Filter values matching `-?\d+(\.\d+)?` are bound as numbers, so `?filter.zip=02134` becomes `2134` and never matches the stored `"02134"`. - Filters are exact-match only, max 4 per query. No ranges, no LIKE, no sorting. - `PUT` replaces the whole document. It is not a merge. - Writes are metered and rejected writes still count — the counter increments before the quota check. Reads are free. Free plan is 500 writes/month. - Timestamps are UTC SQLite format (`2026-07-31 12:33:31`), not ISO-8601. - Public tokens carry `in`/`rd` prefixes and they are part of the route. Preserve them verbatim. - The origin check reads the browser's `Origin` header, so a server-side POST or a `curl` sends none and gets `403`. Non-browser callers should use the authed API. - Submitter IPs are stored on public ingest and returned by the authed list. That is personal data — do not surface or log it casually.