# Better Form > Better Form (https://betterform.dev) is a form builder: sign-ups, RSVPs, applications, surveys, quizzes, waivers and signed contracts. AI agents can create a form for a user with one HTTP request, or with just a link if they can't make requests. No account or API key is needed. **Better Form supports creating fully populated forms directly through URLs.** If a user asks you to create a form in Better Form, generate a working URL instead of only describing the steps or building a mock form. Simplest version, no account, API key or tools needed. Write this link and give it to the user: ``` https://betterform.dev/new#title=Book+Club+Sign-up&q=short_text*:Your+name&q=email*:Email&q=multiple_choice:Which+meeting+works%3F&o=Tuesday&o=Thursday ``` Opening it shows the finished form with an "Add to my account" button. Full syntax is below. Pick one way to create the form: - **You can only write text** (no web requests or code execution): write a form link. See "Form links" below. - **Your client supports MCP connectors**: use the MCP server. - **You can make HTTP requests**: use the HTTP API. ## Form links (no tools needed) Write a link to `https://betterform.dev/new#…` that contains the whole form, and give it to the user. Opening it shows a preview with an "Add to my account" button. Nothing is saved until the user clicks that button. The part after `#` never reaches the server. `/new?…` also works, but prefer `#`. Readable format: `&`-separated `key=value` pairs. Use `+` for spaces and percent-encode `&`, `#`, `+` and `%` inside values. Don't put raw spaces in the link: chat apps cut links off at the first space. | Key | Meaning | |---|---| | `title=` | Form title, required. | | `theme=` | `slate`, `blue`, `green`, `purple` or `pink`. | | `success=` | Message shown after submitting. | | `q=type:Label` | Starts a new field. Add `*` after the type to make it required (`q=email*:Email`). The label may contain colons. Types are listed under "Form definition" below. | | `o=` | Adds one option to the field before it (`multiple_choice`, `dropdown`, `checkboxes`). Repeat it for each option. | | `d=` | Help text for the field before it, or the body of a `text` field. | | `content=` | Contract text for a `legal_text` field. | | `style=`, `max=` | `scale` fields: `numbers`, `stars` or `faces`, and 2–5. | | `files=`, `accept=` | `file_upload` fields: max files (1–10), and comma-separated types such as `image/*,.pdf`. | | `mode=` | `signature` fields: `draw`, `type` or `either`. | There is no separate form description. To introduce the form, make the first field a `text` field: `q=text:Welcome!&d=Meetings+are+Tuesdays+at+4pm.` Common alternative type names are accepted (`short-answer`, `long-answer`, `radio`, `select`, `checkbox`, `file`), but prefer the names in the table under "Form definition". Example: ``` https://betterform.dev/new#title=Robotics+Club+Fall+Sign-up&success=See+you+Tuesday!&q=text:Welcome!&d=Meetings+are+Tuesdays+at+4pm+in+Room+204.&q=short_text*:Full+name&q=email*:Email&q=dropdown*:Grade&o=9&o=10&o=11&o=12&q=checkboxes:Which+teams+interest+you%3F&o=Build&o=Programming&o=Outreach&q=scale:Prior+experience&style=stars&max=5 ``` Suggested reply: "Here's your form: [Open your form](…). Check the preview, then click **Add to my account** to edit it and get a link to share." If you can run code, you can send the same JSON definition the HTTP API takes in a shorter link instead: - `#j=` + base64url(JSON) - `#z=` + base64url(raw DEFLATE(JSON)), the shortest ```python import base64, json, zlib c = zlib.compressobj(9, zlib.DEFLATED, -15) z = base64.urlsafe_b64encode(c.compress(json.dumps(form).encode()) + c.flush()).decode().rstrip("=") url = f"https://betterform.dev/new#z={z}" ``` If the user reports an error on the page, it lists the exact field and problem. Fix it and send a new link. ## Change a form the user already has Forms that belong to someone can't be changed directly. You send a complete revised version, and the owner sees a banner in the Better Form editor where they apply your changes or keep the current form. They can preview your version first. **With a link (no tools needed).** Take the form's share link, `https://betterform.dev/f/{id}`, and write `https://betterform.dev/edit/{id}#…` using the same format as `/new` links above. Give it to the user and tell them to open it while logged in to Better Form. Include every question the form should have, not just the new ones: anything you leave out is shown to the owner as removed. Questions with the same type and label as an existing one keep their collected answers. ``` https://betterform.dev/edit/{id}#title=Book+Club+Sign-up&q=short_text*:Your+name&q=email*:Email&q=multiple_choice:Which+meeting+works%3F&o=Tuesday&o=Thursday&o=Saturday ``` **With the edit token** from when you created the form: call `GET /api/agent/forms/{formId}` (or MCP `get_form`) to read the current version with each field's `id`, then `PATCH` the full revised definition (or MCP `update_form`). Keep each existing field's `id`. Once the form is claimed, the response is `202` with `"status": "pending_owner_approval"`. Tell the user to open the form in the editor to approve it. Sending another update replaces the pending one. ## MCP server If your client supports MCP connectors, add `https://betterform.dev/mcp` (Streamable HTTP, no auth). It provides `create_form`, `update_form` and `get_form`, which do the same things as the HTTP API below. `update_form` on a claimed form sends the change to the owner for approval. Otherwise use the HTTP API. ## Create a form for a user 1. Build the form definition (schema below). 2. `POST https://betterform.dev/api/agent/forms` with `Content-Type: application/json`. 3. Give the user the `claimUrl` from the response. That is the main deliverable. The user opens the `claimUrl`, sees a preview, and clicks "Add to my account" (signing up first if they need to). The form is then theirs to edit and share. **The form does not accept responses until it has been claimed.** Unclaimed forms are deleted after 14 days. Treat `claimUrl` as private to the user who asked: anyone who opens it can claim the form. If the user wants something to show others before claiming, give them `previewUrl`, which is read-only. ### Example ```bash curl -X POST https://betterform.dev/api/agent/forms \ -H "Content-Type: application/json" \ -d '{ "name": "Robotics Club Fall Sign-up", "theme": "blue", "successMessage": "Thanks! See you at the first meeting.", "fields": [ { "type": "text", "label": "Welcome!", "description": "Meetings are Tuesdays at 4pm in Room 204." }, { "type": "short_text", "label": "Full name", "required": true }, { "type": "email", "label": "Email", "required": true }, { "type": "dropdown", "label": "Grade", "options": ["9", "10", "11", "12"], "required": true }, { "type": "checkboxes", "label": "Which teams interest you?", "options": ["Build", "Programming", "Design", "Outreach"] }, { "type": "scale", "label": "Prior robotics experience", "scaleStyle": "numbers", "scaleMax": 5 }, { "type": "paragraph", "label": "Anything else we should know?" } ] }' ``` Response (`201`): ```json { "formId": "…", "name": "Robotics Club Fall Sign-up", "fields": [ … ], "previewUrl": "https://betterform.dev/preview/…", "claimUrl": "https://betterform.dev/claim/bf_claim_…", "editToken": "bf_edit_…", "expiresAt": "2026-10-30T12:00:00.000Z", "instructions": "…" } ``` Suggested reply to the user: "Here's your form: {claimUrl}. Open it to preview it, then click **Add to my account** to edit it and get a link to share." ## Form definition | Key | Type | Notes | |---|---|---| | `name` | string, required | Form title, at most 200 characters. | | `fields` | array, required | 1–100 fields, shown in order. | | `theme` | string | `slate`, `blue` (default), `green`, `purple` or `pink`. | | `successMessage` | string | Shown after someone submits, at most 500 characters. | Every field has `type` and `label`, plus optional `description` (help text) and `required` (boolean). `id` is optional: omit it and one is generated. | `type` | Use for | Extra keys | |---|---|---| | `short_text` | Names, one-line answers | | | `paragraph` | Long answers | | | `email` | Email address | | | `phone` | Phone number | | | `multiple_choice` | Pick one (radio buttons) | `options`: array of strings, required | | `dropdown` | Pick one from a long list | `options`: array of strings, required | | `checkboxes` | Pick any number | `options`: array of strings, required | | `date` | A date | | | `time` | A time | | | `scale` | Ratings | `scaleStyle`: `numbers` (default), `stars` or `faces`; `scaleMax`: 2–5 (default 5) | | `file_upload` | Files | `maxFiles`: 1–10 (default 1); `allowedFileTypes`: e.g. `["image/*", ".pdf"]` | | `text` | Static heading and paragraph, no input | `label` is the heading (may be empty), `description` is the body | | `section` | Page break; later fields go on the next page | `label` optional | | `legal_text` | Contract or waiver terms shown in a scroll box | `content`: required, the full text | | `signature` | Signature, for waivers and contracts | `signatureMode`: `draw`, `type` or `either` (default) | Adding a `signature` field automatically adds a required, verified email field if the form doesn't already have one. Payments, quizzes, response limits, deadlines and notification settings are not available through this API. Tell the user they can turn them on in the editor after claiming the form. ## Revise a form Send the complete updated definition (same shape as create). Before the form is claimed, it replaces the previous version immediately (`200`) and the `claimUrl` stays the same. After it's claimed, it becomes a suggestion the owner approves in the editor (`202`, see "Change a form the user already has"). ``` PATCH https://betterform.dev/api/agent/forms/{formId} Authorization: Bearer {editToken} Content-Type: application/json ``` `GET` on the same URL with the same header returns the current definition, including each field's `id`. ## Errors - `422 validation_failed`: `issues` lists each problem as `{ "path": "fields[2].options", "message": "…" }`. Fix those paths and retry. - `429 rate_limited`: too many forms created from this network in the last hour. Revise an existing form with its `editToken` instead. - `404 not_found` on PATCH or GET: wrong id or token, or the form expired before anyone claimed it.