# Formstep Docs (Complete) --- ## Getting Started # Sign up & workspace Create your account, set up your workspace, and invite your team. Sign in with Google or a magic link, with no password or credit card needed. ## Create your account Open [app.formstep.io](https://app.formstep.io) and sign in with Google, which also takes Google Workspace accounts. Or type your email and click **Continue**: Formstep shows _Check your email_ and sends you a magic link, and opening it signs you in. Neither way needs a password. Signing in for the first time creates your account. You do not need a credit card, and there is no trial that runs out. ![The sign-in page: Log in or create your account, with a Google button, an email field, and Continue](https://formstep.io/docs/images/introduction/login.png) One page signs you in and creates your account. ## Your first workspace A **workspace** holds your forms, requests, submissions, integrations, and teammates. Your first sign-in opens one made for you, **My Workspace**, with a published **Approval** form and its share link to try things on. The timezone comes from your browser. Rename the workspace after your company, your team, or _Personal_, and change the timezone, in **Workspace Settings**. ![The dashboard of a new account: My Workspace in the sidebar and the Approval form, published, on the page](https://formstep.io/docs/images/introduction/create-workspace-landing.png) A new account opens in My Workspace. Every plan includes unlimited workspaces, for example one per client or brand. See [more than one workspace](https://formstep.io/docs/workspaces-teams/overview/#multiple-workspaces) and [how accounts, workspaces, and forms fit together](https://formstep.io/docs/getting-started/faq/#workspace-vs-account). ## Invite teammates (optional) Open the workspace switcher at the top of the sidebar, then **Workspace Settings**. Under **Members**, click **Invite** and share the link it gives you. Members are unlimited on every plan. See [inviting members](https://formstep.io/docs/workspaces-teams/inviting-members/). ## Next steps Next, build a form and publish it. Requests and share links both start from a published form. [Build your form](https://formstep.io/docs/getting-started/build-your-form/) — Add questions and publish [Inviting members](https://formstep.io/docs/workspaces-teams/inviting-members/) — Invite links, who can do what, and removing members [Plans & pricing](https://formstep.io/docs/subscription-billing/plans-pricing/) — Compare Free, Pro, and Business features [Getting started FAQ](https://formstep.io/docs/getting-started/faq/) — Accounts, workspaces and moving a form # Build your form Go from an empty canvas to a published form. Every request and every share link starts from one. ## Publish in four steps The editor works like a document: type where you want text, and add questions from the Blocks tab or with the `/` key. 1. **Start a new form** Open **Forms** in the sidebar and click **+ Form**, then pick **Start empty**. The same screen offers **Use a template** and **Build with AI**, which opens the AI chat in the bottom-right corner. To try the editor without an account, go to [app.formstep.io/try](https://app.formstep.io/try). 2. **Add questions** Click a block in the **Blocks** tab on the right, or press **/** in the form and pick one. Type the label, then press Enter. Drag the handle on the left of a block to move it. 3. **Preview** Click **Preview** in the top bar to answer the form as your recipient will. 4. **Publish** Click **Publish**, top right. The form goes live and opens on its own page, with **Public link** and **New request** at the top. In the editor, **Inbox** takes you back to that page. ![A new form's start screen with three cards: Start empty, Use a template, and Build with AI](https://formstep.io/docs/images/introduction/new-form-start.png) A new form asks how to start. Build with AI opens the AI chat in the bottom-right corner. ![The editor with the slash menu open in the form and the Blocks tab on the right](https://formstep.io/docs/images/introduction/empty-editor.png) Add a question with / in the form, or from the Blocks tab on the right. > **Other ways to start** > > Open **Templates** in the sidebar for ready-made forms, or open AI chat, bottom right, and describe the form you need. See [Use AI chat](https://formstep.io/docs/ai/using-ai/). ## What publishing does Each publish saves a version. People who already opened the form keep that version while you edit the draft, and you can see every version in [Publish history](https://formstep.io/docs/building-forms/version-history/). Publishing also creates the form’s first share link and turns on **New request**. > **Safe to experiment** > > Edits save as a draft. People answering your form see the last published version until you publish again. ## Send it or share it Send a [request](https://formstep.io/docs/getting-started/send-your-first-request/) when one person owes you answers and you want to know when they reply. Use the [public link](https://formstep.io/docs/getting-started/share-a-public-link/) when anyone may answer, as often as you allow. The same form can go out both ways at once. ## Next steps [Send your first request](https://formstep.io/docs/getting-started/send-your-first-request/) — Assign the form you just published to one person [Understanding the editor](https://formstep.io/docs/building-forms/understanding-the-editor/) — Top bar, side panel, blocks, theme, and settings [Question types](https://formstep.io/docs/building-forms/field-types/) — Text, choice, file upload, payment, decision, and more [Use AI chat](https://formstep.io/docs/ai/using-ai/) — Build a form from one prompt # Send your first request Send your published form to one person, with what you already know filled in. Try it on yourself first, then find the answers on the Requests page. ## Make a request and open it yourself 1. **Open the New request sheet** Open your published form and click **New request**, the paper plane icon at the top right of the form page. 2. **Fill it in** Under _Who is it for?_, type your own email and name. Under _What they see_, fill in answers you already know, and click **Lock** on any the recipient may only confirm. 3. **Click Try it yourself** At the bottom of the sheet, **Try it yourself** makes a [test request](https://formstep.io/docs/requests/creating-requests/#test-mode) and shows its link. A test request emails nobody and does not count toward your monthly allowance. ![The New request sheet with a recipient, two prefilled answers, one of them locked, and Try it yourself in the footer](https://formstep.io/docs/images/requests/request-sheet.png) New request: who it is for, what they see, and Try it yourself at the bottom. ## Answer it as the recipient Open the link and answer the form. It shows your theme, logo and language, and asks for no captcha or sign-in. When you submit, you see your thank-you page. A request is answered once, so opening the link again shows that it is done. ## Find the answers Click **Requests** in the sidebar. Test requests are hidden, so open **Filter** and tick **Show test requests**. Click your request to see its answers and its timeline: created, opened, started and completed. ![The Requests page: the list on the left and one request open on the right with its answers, timeline, and callback](https://formstep.io/docs/images/requests/requests-page.png) The Requests page: the list on the left, the open request on the right. From the request you can copy the link, send a reminder, or cancel it. See [the Requests page](https://formstep.io/docs/requests/managing-requests/). ## Send it to someone else Fill in the recipient’s details instead of your own and choose **Email** or **Link**. [Send a request](https://formstep.io/docs/requests/creating-requests/) explains each part of the sheet. > **Automate this** > > A workflow or an AI agent can send the same request for you and pick up the answers. See [Guides](https://formstep.io/docs/guides/overview/). ## Next steps [See answers arrive](https://formstep.io/docs/getting-started/see-answers-arrive/) — Where answers from both channels land [Send a request](https://formstep.io/docs/requests/creating-requests/) — Prefill, locked fields, hidden fields and Email or Link # Share a public link Open the same form to anyone: copy the link, embed it, or print a QR code. Give each link its own language, limits, and parameters, and track it in analytics. ## Share link or request? Use a share link when anyone may answer. Use a [request](https://formstep.io/docs/getting-started/send-your-first-request/) when one named person owes you answers and you want to follow up. Both spend the same monthly allowance and land in the same inbox. See [public link or request](https://formstep.io/docs/sharing-publishing/overview/#public-link-or-request). ## Copy the link 1. **Open Public link** Open your published form and click **Public link**, the link icon at the top right of the form page. Publishing already created the first link. 2. **Copy it** Click **Copy link**. The link is live: open it in a new tab and answer once, so you have a row to look at on the next page. 3. **Add more links if you need them** **Create new link** adds another link with its own settings and analytics, for example one for your newsletter and one for social media. Only you see the link name. ![The Public link sheet with the link, the QR Code, Embed, and Popup cards, and the Link, Appearance, Limits, and Query parameters sections](https://formstep.io/docs/images/building-forms/share-panel.png) Public link: the link, three more ways to share it, and its settings. ## Ways to share it | Way | Good for | Where to find it | | - | - | - | | Link | Email, chat, social posts | Copy link, at the bottom of the sheet | | Embed or popup | Your own website | The Embed and Popup cards | | QR code | Print, packaging, an event stand | The QR Code card | ## Link settings Each share link has its own settings in the sheet. Give it a name that only you see, and on Pro an address on [your own domain](https://formstep.io/docs/branding-domains/custom-domains/). Choose the language it opens in and the preview shown when it is shared. Set a closing date or a [maximum number of responses](https://formstep.io/docs/sharing-publishing/scheduled-close/#response-limits), and add [tracking tags](https://formstep.io/docs/sharing-publishing/sharing-embedding/) to tell campaigns apart. **Revoke** stops a share link from taking answers and keeps its analytics. You can turn it back on later. ## Next steps [See answers arrive](https://formstep.io/docs/getting-started/see-answers-arrive/) — Where responses from both channels land [Embedding & popups](https://formstep.io/docs/sharing-publishing/embedding-popups/) — Put the form on your own site [Response limits](https://formstep.io/docs/sharing-publishing/scheduled-close/#response-limits) — Cap how many responses a link accepts [Your own domains](https://formstep.io/docs/branding-domains/custom-domains/) — Serve the form from your own domain (Pro) # See answers arrive One inbox for both channels: where answers land, how to get notified, and how to route them onward. ## One inbox for both channels Open the form from the Forms list. A published form opens on its own page, on the **Submissions** tab. From the editor, **Inbox** takes you there. ![The right side of the form page's top bar: the Requests and Submissions tabs, then the Public link, New request, Integrations, and Settings icons, and Edit](https://formstep.io/docs/images/introduction/form-page-top-bar.png) The form page's top bar. The icons, left to right: Public link, New request, Integrations, and Settings. Every answer is in the **List** view. The **Channel** column shows how it arrived: **Public link** or **Request**. A request row links back to its request. ![The List view on the Submissions tab, with one row per response and a Channel column](https://formstep.io/docs/images/introduction/submissions.png) The Submissions tab: one row per response, with the channel it came through. Next to **List**, **Summary** shows charts and counts per question, and **Analytics** shows views, completion rate, devices and traffic sources (Pro). The **Requests** tab, next to Submissions, shows the form’s requests: who has not opened theirs yet, which reminders went out, and whether your workflow heard back. See the [submission inbox](https://formstep.io/docs/submissions-analytics/submission-inbox/). ### Answers to a test request A test request, such as the one **Try it yourself** makes, keeps its answers out of the Submissions tab, exports and integrations. To see them, open the form’s **Requests** tab, or **Requests** in the sidebar, then open **Filter**, tick **Show test requests**, and click the request. ## Get an email for each answer Click **Settings**, the gear icon, and open the **Emails** tab. Turn on **Notify on new submission**. Your own address is already filled in, and this works on Free. On Pro, you can add more recipients and attach a PDF of the answers. See [self notifications](https://formstep.io/docs/submissions-analytics/self-notifications/). ![The Emails tab of form settings with Notify on new submission turned on](https://formstep.io/docs/images/introduction/notifications.png) Settings, Emails tab. To send the person who answered a confirmation, turn on **Send confirmation to respondent** (Pro). On a request, it goes to the recipient. See [respondent notifications](https://formstep.io/docs/submissions-analytics/self-notifications/#respondent-confirmation). ## Send answers to other tools An integration sends every submission to another tool as it arrives, such as [Google Sheets](https://formstep.io/docs/integrations/google-sheets/), [Notion](https://formstep.io/docs/integrations/notion/), [Airtable](https://formstep.io/docs/integrations/airtable/), [HubSpot](https://formstep.io/docs/integrations/hubspot/), [Slack](https://formstep.io/docs/integrations/slack/) or your own [webhook](https://formstep.io/docs/integrations/webhooks/). Click **Integrations**, the plug icon, to add one. See [all integrations](https://formstep.io/docs/integrations/overview/). A workflow that sent a request also hears back when that request ends, with its answers. See the [requests overview](https://formstep.io/docs/requests/overview/). ## Next steps [Understanding the dashboard](https://formstep.io/docs/getting-started/dashboard/) — Sidebar, workspace switcher, AI, and where every setting lives [Submission inbox](https://formstep.io/docs/submissions-analytics/submission-inbox/) — Filter, search, sort, and review each response [Email notifications](https://formstep.io/docs/submissions-analytics/self-notifications/) — Get emailed when new responses come in [Respondent notifications](https://formstep.io/docs/submissions-analytics/self-notifications/#respondent-confirmation) — Confirm with a custom subject, body, and PDF # Understanding the dashboard Find your way around Formstep outside the editor: the sidebar, switching workspaces, your plan and AI credits, AI chat, and where each setting lives. ## The layout The sidebar on the left holds the workspace switcher, the pages, your plan and AI credits, and your name. The page you open fills the rest, and Formstep opens on **Forms**. **Ask anything**, bottom right, opens AI chat over any page. Two shortcuts work anywhere: `⌘`+`K` / `Ctrl`+`K` opens search, and `⌘`+`A` / `Ctrl`+`A` opens and closes AI chat. ![The dashboard: the sidebar on the left and the Forms page with folders and forms](https://formstep.io/docs/images/introduction/dashboard.png) The sidebar on the left, folders and forms in the main area. ## Workspace switcher Click the workspace name at the top of the sidebar to switch workspaces, create one, open **Workspace Settings**, or leave a workspace you don’t own. Only the owner opens Workspace Settings, which holds the name, timezone, email language, members and AI credit sharing. See the [workspaces overview](https://formstep.io/docs/workspaces-teams/overview/). ![The workspace menu open from the top of the sidebar: two workspaces marked Owner, then Create workspace, Workspace Settings, and Leave workspace](https://formstep.io/docs/images/introduction/workspace-switcher.png) The workspace switcher, at the top of the sidebar. ## Sidebar pages - Forms lists your forms and [folders](https://formstep.io/docs/workspaces-teams/folders/), with **+ Form** and **+ Folder** to add more. - Requests lists every request in the workspace with its status. See [the Requests page](https://formstep.io/docs/requests/managing-requests/). - [Trash](https://formstep.io/docs/workspaces-teams/trash/) keeps deleted forms for 30 days. - Domains is where the owner adds [your own domains](https://formstep.io/docs/branding-domains/custom-domains/) for form links and emails (Pro). - Integrations holds the accounts you connected, such as Google Sheets or Slack, for use on any form. See [integrations](https://formstep.io/docs/integrations/overview/). - OAuth and API Keys is for connecting your own tools and AI agents. See [Connect an AI agent](https://formstep.io/docs/guides/ai-agents/connect/). Below them, **Templates** copies a ready-made form into the workspace, **Help** links to these docs and to support, and **Feedback and Roadmap** shows what is coming. ## Plan, credits, theme and language The card above your name shows your plan, with **Upgrade** or **Manage**, and about how many AI chats your credits cover, with **Add credits**. Credits belong to you, not to the workspace. See [AI credits](https://formstep.io/docs/ai/ai-credits-usage/). It also switches the dashboard between light and dark, and sets the dashboard language. Your forms keep [their own language](https://formstep.io/docs/building-forms/translating-form-content/#source-language). ![The account card with plan, AI credits, theme, and language rows](https://formstep.io/docs/images/introduction/sidebar-user-area.png) The account card, above your name. ## Your account Click your name at the bottom of the sidebar for **Settings** and **Log out**. Settings holds your photo, display name, sign-in method and **Delete Account**. ## Form settings A form’s own settings are on the form page and in the editor: the gear icon opens [Settings](https://formstep.io/docs/building-forms/form-settings/), and the plug icon next to it opens the form’s [integrations](https://formstep.io/docs/integrations/overview/). ## Next steps [Understanding the editor](https://formstep.io/docs/building-forms/understanding-the-editor/) — Where everything is in the form editor [Use AI chat](https://formstep.io/docs/ai/using-ai/) — Build, edit and analyze forms with AI [Workspaces overview](https://formstep.io/docs/workspaces-teams/overview/) — Members, settings and more than one workspace [Getting started FAQ](https://formstep.io/docs/getting-started/faq/) — Accounts, workspaces and moving a form # Getting started FAQ Short answers to common first questions: whether respondents need an account, how accounts and workspaces differ, moving a form, and deleting your account. ## Do respondents need a Formstep account? No. Anyone with the link can answer without an account. You can protect a share link with a password, or ask people to sign in first with an [authentication gate](https://formstep.io/docs/sharing-publishing/authentication-gate/). ## What’s the difference between an account, a workspace and a form? Your account is you: your email, your plan and your AI credits. A workspace is a shared space for forms, requests, submissions, members, domains and integrations, and you can have as many as you like. A form holds questions, logic and a theme inside a workspace, and reaches people through requests and share links. ## Can I use Formstep without AI? Yes. You can build every form by hand, and credits are spent only when you use AI. Without credits, everything except AI keeps working. ## Can I use Formstep on mobile? Yes. The dashboard and the editor work in mobile browsers, and your forms fit any screen. ## Can I move a form or a workspace to someone else? Not from the app. You can duplicate a form inside its workspace and invite people to the workspace. To move a form to another workspace, or to hand over a workspace, email [support](https://formstep.io/docs/getting-started/support/). ## Which tools does Formstep work with? Submissions go to Google Sheets, Notion, Airtable, HubSpot, Slack, Discord, Linear, GitHub and webhooks. Zapier, Make and n8n can send requests and pick up answers. Cal.com adds bookings to a form, and Google Analytics and Axiom receive form analytics. All of them work on every plan. See [integrations](https://formstep.io/docs/integrations/overview/). ## Can I delete my account and all my data? Yes. Click your name at the bottom of the sidebar, then **Settings**. Under **Danger Zone**, click **Delete** and type your email address. This removes your forms, submissions, files and the workspaces you own, and cancels a paid plan. You are signed out at once, and for 24 hours you can stop it by signing in again and clicking **Keep my account**. After that, it cannot be undone. ## Next steps If your question is not here, email support. [Get support](https://formstep.io/docs/getting-started/support/) — What to send so we can help quickly [Plans & pricing](https://formstep.io/docs/subscription-billing/plans-pricing/) — What each plan includes # Get support How to reach Formstep when you need help with your account, a form or a connected assistant. Email us, say what happened, and leave out passwords and API keys. Email . Tell us what you were trying to do, what happened instead, and the name of the workspace and form. If you use Formstep through ChatGPT, Claude or another assistant, also tell us which one and what you asked it to do. Don’t send passwords, API keys or a recipient’s answers. ## Problems with a connected assistant If the assistant can’t reach your forms, check that you connected the right workspace. A connection reaches only the workspace you chose when you signed in. To remove a connection, open OAuth and API Keys in Formstep and revoke it. The assistant loses access straight away. To set a connection up, see [Connect an AI agent](https://formstep.io/docs/guides/ai-agents/connect/). ## Your personal data To ask about personal data Formstep holds, or to use your rights under the [Privacy Policy](https://formstep.io/docs/legal/privacy-policy/), email the same address. If you filled in someone else’s form and want an answer changed or deleted, contact the person or company who sent you the form. They decide what happens to the answers they collect. ## Next steps [Getting started FAQ](https://formstep.io/docs/getting-started/faq/) — Answers to the first questions people ask [Connect an AI agent to Formstep](https://formstep.io/docs/guides/ai-agents/connect/) — Set up ChatGPT, Claude or another assistant --- ## Building Forms # Building Forms overview You build a form in an editor that works like a document, add questions, content and logic, and publish when it is ready. ## How a form is built The editor works like a document: you type, add blocks, and move them around. Questions and content sit on the canvas as respondents will see them. Logic, hidden fields, and calculated fields work in the background, so the form can react to each answer. AI chat can build or change a form for you from a sentence. ![The form editor: the form on the canvas with its questions, the Blocks panel on the right listing every question and block, and the top bar with Edit, Translate, Preview and Publish changes.](https://formstep.io/docs/images/building-forms/editor-layout.png) The editor: the form on the canvas, the blocks beside it. ## Draft and published Your edits save as a draft. Respondents keep seeing the last published version until you publish again, so you can work on a live form without anyone noticing. Before you publish, Formstep checks the draft and stops on anything that would break. Use Preview to try the form as a respondent first. ## Pages in this section The editor - [Understanding the editor](https://formstep.io/docs/building-forms/understanding-the-editor/) — The parts of the form editor: the top bar, the canvas, the side panel and its four tabs, the slash menu, AI chat, and keyboard shortcuts. - [Form structure](https://formstep.io/docs/building-forms/form-structure/) — Split a form into pages with a progress bar, put blocks side by side in columns, and move, change, or hide blocks with the block menu. - [Question types](https://formstep.io/docs/building-forms/field-types/) — The questions you can add to a form, from text and choice to signatures, payments, and scheduling, and when to use each one. - [Question settings](https://formstep.io/docs/building-forms/field-configuration/) — Labels, placeholders, default values, choice options, and validation rules. - [Content blocks & formatting](https://formstep.io/docs/building-forms/content-blocks/) — Add headings, text, images, videos, maps, embeds, and tables between your questions, format text with the toolbar, and show earlier answers with @. Files, payments and more - [File uploads & signatures](https://formstep.io/docs/building-forms/file-uploads-signatures/) — Collect files and drawn signatures from respondents. - [Documents block](https://formstep.io/docs/building-forms/documents-block/) — Hand files to the respondent (a contract draft, a price list, a how-to) as a download list inside the form. - [Payment collection](https://formstep.io/docs/building-forms/payment-collection/) — Accept one-time payments from respondents via Stripe. - [Respondent email verification](https://formstep.io/docs/building-forms/email-verification/) — On the Business plan, respondents confirm their email address with a 6-digit code before they can submit, so every address you collect is real and reachable. - [Repeating groups](https://formstep.io/docs/building-forms/repeating-groups/) — Let a respondent fill in the same questions as many times as they need, such as one name and age per guest, and see where the answers show up. Logic and data - [Conditional logic](https://formstep.io/docs/building-forms/conditional-logic/) — Show, hide, require, and jump to pages based on answers, or calculate values and stop a submission. - [Hidden fields](https://formstep.io/docs/building-forms/hidden-fields/) — Pass data the respondent never sees into a form, from the address of a share link or the context of a request, and save it with each submission. - [Calculated fields](https://formstep.io/docs/building-forms/calculated-fields/) — Work out scores, totals, and text from form answers. - [@ mentions](https://formstep.io/docs/building-forms/answer-piping/) — Type @ to show an answer somewhere else in your form, from question titles to the thank-you page and emails. - [AI Validation](https://formstep.io/docs/building-forms/ai-validation/) — Check answers and uploaded files against a rule you write in one sentence. After they submit - [Thank-you page & redirect](https://formstep.io/docs/building-forms/custom-thank-you/) — Customize the thank-you page respondents see after they submit, with text, links, images, and their answers, or send them to your own web page. - [Live results](https://formstep.io/docs/building-forms/live-results/) — Show respondents the answer counts after they submit, put the results on a big screen, and add an AI summary of what they mean. Languages and publishing - [Multilingual forms](https://formstep.io/docs/building-forms/translating-form-content/) — Set the language you write a form in, translate questions, text, and emails by hand or with AI, and control which language each respondent sees. - [Form settings](https://formstep.io/docs/building-forms/form-settings/) — Choose who can open a form, what happens after someone submits, and which emails go out, all in the Settings dialog. - [Publish history](https://formstep.io/docs/building-forms/version-history/) — View the published versions of your form and revert to an earlier one. - [Publish checks](https://formstep.io/docs/building-forms/publish-checks/) — What Formstep checks before your form goes live, which findings block publishing, and how to clear the ones you are most likely to meet. # Understanding the editor The parts of the form editor: the top bar, the canvas, the side panel and its four tabs, the slash menu, AI chat, and keyboard shortcuts. ## Layout The editor works like a document: you type, add blocks, and move them around. It has four areas. ![The form editor on a published form: the top toolbar with Edit, Translate, and Preview, then Publish history, Integrations, Settings, the green issue indicator, Publish changes, and the primary Inbox button; the canvas in the center; and the side panel on the right, open on its Blocks tab, with the rail of Blocks, Fields, Logic, and Theme icons at the edge.](https://formstep.io/docs/images/building-forms/editor-layout.png) The editor: top bar, canvas, and side panel. - The top bar switches between **Edit**, **Translate**, and **Preview**. On the right are Publish history, Integrations, Settings, the issue indicator, and **Publish**. - The canvas, in the center, shows your form as respondents see it. - The side panel, on the right, has four tabs. See [Side panel](#side-panel). - AI chat opens from the button at the bottom right. See [AI chat](#ai-chat). More on the top bar: - **Translate**: [Multilingual forms](https://formstep.io/docs/building-forms/translating-form-content/) - **Preview** lets you fill in the form as a respondent would, with logic working. Nothing is saved, and uploads and payments are simulated. - **Publish history**: [Publish history](https://formstep.io/docs/building-forms/version-history/) - **Settings**: [Form settings](https://formstep.io/docs/building-forms/form-settings/). **Integrations**: [Integrations](https://formstep.io/docs/integrations/overview/) - The issue indicator and **Publish**: [Publish checks](https://formstep.io/docs/building-forms/publish-checks/) - **Inbox**, once the form is published, opens the form's own page with its requests and submissions. See the [submission inbox](https://formstep.io/docs/submissions-analytics/submission-inbox/). ## Side panel Click an icon on the rail to open a tab, and click it again to close the panel. On a wide screen, drag the panel's left edge to resize it. | Tab | What it holds | | - | - | | Blocks | Every block you can add, with a search box. Click a block to add it below the cursor, or drag it onto the canvas. See [Question types](https://formstep.io/docs/building-forms/field-types/) | | Fields | The field keys automations read and prefill your answers by. Click a name to jump to its question. See [Field keys](https://formstep.io/docs/requests/field-keys/) | | Logic | Every logic rule in plain words. Click a rule to go to it, or add one. See [Conditional logic](https://formstep.io/docs/building-forms/conditional-logic/) | | Theme | Colors, fonts, presets, and dark mode. See [Appearance & theming](https://formstep.io/docs/branding-domains/appearance-theming/), which also covers the cover and logo | On a narrow window, the panel opens from the bottom. On a phone, Fields and Theme are in the top bar's **⋯** menu, and you add blocks by typing `/`. ## Slash menu To add a block from the keyboard, type `/` on an empty line, or press `⌘`+`/` ( `Ctrl`+`/` ), and keep typing to filter. The menu shows only the blocks that fit where the cursor is. ![The slash menu open on the canvas after typing a slash, showing its block categories, Basic Blocks and Form Fields first, with their listed block types.](https://formstep.io/docs/images/building-forms/slash-menu.png) Slash menu: the block picker. To change a block you added, click its drag handle (⋮⋮). See [Form structure](https://formstep.io/docs/building-forms/form-structure/) and [Question settings](https://formstep.io/docs/building-forms/field-configuration/). ## AI chat The AI mascot at the bottom right opens AI chat, and a second click closes it. It can write, check, and edit your form: say “add a phone number and a date picker” and it adds them. It uses your AI credits. See [Use AI chat](https://formstep.io/docs/ai/using-ai/). ![The AI chat floating over the bottom-right of the editor, beside the side panel's rail, showing starter prompt suggestions, the message input, and the remaining AI credits above the input; the AI mascot that opens and closes it stands under the rail.](https://formstep.io/docs/images/building-forms/ai-chat-panel.png) AI chat, floating over the editor. ## Keyboard shortcuts On a Mac use Cmd; on Windows and Linux use Ctrl. | Shortcut | What it does | | - | - | | Cmd + / | Open the block menu at the cursor | | Cmd + B / I / U / S | Bold, italic, underline, strikethrough | | Cmd + K | On the canvas: link the selected text. Anywhere else: search for any form, folder, workspace, or setting | | Cmd + A | Once: the current block's text. Again: every block | | Shift + Enter | Line break inside the block, instead of a new block | | Cmd + Enter | Leave the block and start a new paragraph below | | Tab / Shift + Tab | Indent or outdent a list item | ## Next steps [Question types](https://formstep.io/docs/building-forms/field-types/) — Every question you can add [Form structure](https://formstep.io/docs/building-forms/form-structure/) — Pages, columns and moving blocks [Publish checks](https://formstep.io/docs/building-forms/publish-checks/) — What blocks a publish, and what only warns [Use AI chat](https://formstep.io/docs/ai/using-ai/) — Build and change a form by asking # Form structure Split a form into pages with a progress bar, put blocks side by side in columns, and move, change, or hide blocks with the block menu. ## Pages A form starts as one page. Split a longer form into pages, so respondents see a few questions at a time. Pages also let logic skip whole steps. ### Add a page 1. **Insert a Page Break** Click Page Break in the Blocks tab, or type /page on the canvas. Everything below it becomes the next page. 2. **Add questions and content** Add any questions, text, columns, and media to each page. 3. **Name the page (optional)** Open the page break’s block menu and pick Set page name. Named pages are easier to choose in logic jumps. ![The slash menu filtered to page blocks after typing /page, offering Page Break and Thank You Page.](https://formstep.io/docs/images/building-forms/pages-columns.png) Type /page to add a page break. Formstep adds **Next** and **Back** buttons, and **Submit** on the last page. **Back** returns to the pages the respondent saw. The [thank-you page](https://formstep.io/docs/building-forms/custom-thank-you/) does not count as a page. ### Progress bar A form with more than one page shows a thin progress bar at the top. It counts every page in the form, so when logic skips pages, the bar jumps ahead. Skipped pages count as hidden: their questions are not required, and their answers are dropped. See [Conditional logic](https://formstep.io/docs/building-forms/conditional-logic/). ## Columns Columns put blocks side by side, for example a name and an email on one row. 1. **Insert a Columns block** Click 2 Columns in the Blocks tab, or type /columns on the canvas. 2. **Add blocks inside each column** Click inside a column and add any block. 3. **Move blocks between columns** Drag a block from one column and drop it into another. To add a third column, drag a block to the side of a row. On phones, columns stack on top of each other. > **What cannot go in a column** > > Logic, Hidden Field, Calculated Field, Page Break, Thank You Page, and Repeating Group only work outside columns. ## Move and change blocks Drag a block by the handle (⋮⋮) on its left edge to move it up, down, to another page, or into a column. Click the handle to open the block menu. It shows only what fits that block, such as **Turn into**, **Options**, **Validation**, **Mark as required**, **Duplicate**, **Move up**, and **Delete**. - **Turn into** changes a question to a related type without losing what you typed. Text inputs convert into each other, choice questions (Ranking and picture choice included) into each other, and Star Rating and Linear Scale into each other. - **Hide** keeps a block in the form but hides it from respondents. Logic can show it later. - **Create repeating group** wraps the block, or the selected blocks, into a [repeating group](https://formstep.io/docs/building-forms/repeating-groups/). To change several blocks at once, drag a rectangle around them or shift-click a second block, then use the handle menu. The settings each question has are on [Question settings](https://formstep.io/docs/building-forms/field-configuration/). ## Next steps [Conditional logic](https://formstep.io/docs/building-forms/conditional-logic/) — Skip pages and show blocks based on answers [Repeating groups](https://formstep.io/docs/building-forms/repeating-groups/) — Questions respondents fill in again and again # Question types The questions you can add to a form, from text and choice to signatures, payments, and scheduling, and when to use each one. Add a question from the **Blocks** tab on the right of the editor, under **Questions**, or type `/` on the canvas and look under **Form Fields**. Hover a block to preview it. The names below are the labels you see there. ![The Blocks tab of the side panel: a search box, the hint to click a block or drag it into place, and the question tiles under Questions, with the rail of Blocks, Fields, Logic, and Theme beside it.](https://formstep.io/docs/images/building-forms/blocks-tab.png) The Blocks tab: click a block or drag it onto the canvas. ## Text and input These questions collect typed answers. Email, Website URL, and Phone Number also check the format as the respondent types. | Type | What it does | Good for | | - | - | - | | Text Input | Single-line text field. Optional minimum and maximum length and a pattern. | Names, job titles, short answers | | Text Area | Multi-line text field that grows as the respondent types. Same rules as Text Input. | Messages, feedback, cover letters | | Email | Checks the address format. Can receive the confirmation email. | Contact forms, registrations | | Website URL | Checks that the answer is a web address. | Portfolios, social profiles | | Phone Number | Country picker with flags; checks the number for that country. | Support requests, order forms | | Number | Numbers only, with an optional minimum and maximum. | Ages, quantities, budgets | Only these six have a [placeholder and a default value](https://formstep.io/docs/building-forms/field-configuration/#common-settings), and you can turn each into any of the others. ## Choice | Type | What it does | Good for | | - | - | - | | Radio Buttons | Vertical list where exactly one option can be picked. | Yes/no questions, plan selection | | Decision | A ready-made choice of approve, decline, or changes, so an automation can act on the verdict. See [Decisions & approvals](https://formstep.io/docs/requests/decisions-and-approvals/) | Approvals, sign-offs, quote acceptance | | Checkboxes | Any number of options can be ticked. | Preferences, wishlists, consent | | Dropdown | Compact picker that opens on click, with search. | Countries, departments, long lists | | Toggle Switch | A switch whose answer is on or off. | Opt-ins, terms acceptance | | Single Picture Choice | Image cards with labels; only one can be picked. | Product selection, visual preference | | Multi Picture Choice | Image cards with labels; several can be picked. | Design preference sets, visual quizzes | ## Rating and scale The Summary tab shows a chart for each of these. | Type | What it does | Good for | | - | - | - | | Star Rating | A row of stars, 5 by default, up to 10. | Satisfaction scores, reviews | | Linear Scale | A numbered scale, up to 10. | NPS, agreement scales | | Ranking | Drag-to-reorder list. Every option is always ranked, so it never blocks submission. | Priority lists, preference order | | Matrix / Grid | Rows are sub-questions, columns are the answers; one column per row. | Satisfaction surveys, comparison grids | ## Date and time | Type | What it does | Good for | | - | - | - | | Date Picker | Calendar popup with an optional earliest and latest date. | Birthdates, event dates, deadlines | | Time Picker | Time-of-day input with an optional earliest and latest time. | Meeting times, availability windows | ## Files and documents | Type | What it does | Good for | | - | - | - | | File Upload | Respondents drag in files or browse for them. You set the accepted types, the size cap, and how many files. See [File uploads & signatures](https://formstep.io/docs/building-forms/file-uploads-signatures/) | Resumes, photos, receipts | | Signature | Respondents draw with a mouse or finger; Formstep stores it as an image. | Contracts, consent forms, waivers | | Documents | Files you give the respondent to download. A request can add files for its one recipient. See [Documents block](https://formstep.io/docs/building-forms/documents-block/) | Contract drafts, price lists, terms | Uploaded files, signatures, and the documents you add count toward your workspace's storage. See [Limits & quotas](https://formstep.io/docs/subscription-billing/limits-quotas/). ## Payments and scheduling | Type | What it does | Good for | | - | - | - | | Payment | One-time payment through Stripe when the respondent submits. Logic can change the amount. See [Payment collection](https://formstep.io/docs/building-forms/payment-collection/) | Registration fees, orders, donations | | Schedule appointment | Book a time from your Cal.com calendar inside the form. See [Cal.com](https://formstep.io/docs/integrations/cal-com/) | Consultations, interviews, demos | > **Looking for CAPTCHA?** > > Bot protection is a form setting, not a question. See [Bot protection](https://formstep.io/docs/sharing-publishing/captcha-bot-protection/). ## Blocks that are not questions - Text and media: Heading 1 to 3, Bullet List, Numbered List, Table, Image, YouTube Embed, Google Maps Embed, and Embed anything. See [Content blocks](https://formstep.io/docs/building-forms/content-blocks/). - Layout: 2 Columns and Page Break (see [Form structure](https://formstep.io/docs/building-forms/form-structure/)), Repeating Group (see [Repeating groups](https://formstep.io/docs/building-forms/repeating-groups/)), and Thank You Page (see [Thank-you page & redirect](https://formstep.io/docs/building-forms/custom-thank-you/)). - Logic and data: [Logic](https://formstep.io/docs/building-forms/conditional-logic/) shows, hides, requires, and jumps. [Hidden fields](https://formstep.io/docs/building-forms/hidden-fields/) and [calculated fields](https://formstep.io/docs/building-forms/calculated-fields/) hold values. [AI Validation](https://formstep.io/docs/building-forms/ai-validation/) checks answers against a rule you write. ## Next steps [Question settings](https://formstep.io/docs/building-forms/field-configuration/) — The settings and rules each question has [Content blocks & formatting](https://formstep.io/docs/building-forms/content-blocks/) — Headings, text, images and tables between questions # Question settings Labels, placeholders, default values, choice options, and validation rules. Set what every question shares, and fill a default from another field with @. ## Where the settings are Type a question's title and options on the canvas. For everything else, click the drag handle on the block's left edge and use **Turn into**, **Options**, or **Validation**. ## Common settings These options appear on every question, whatever its type. | Setting | What it does | | - | - | | Title | The question label respondents see. It supports bold, italic, and links | | Hide title / Show title | Hides the label but keeps the question. Useful when a paragraph above already says what to enter | | Mark as required / Mark as optional | New questions are required. A question that logic hides is never required | | Edit placeholder / Edit default value | The placeholder is a grey hint; the default value is a real answer the respondent can change. Text Input, Text Area, Email, Website URL, Phone Number, and Number have both. Press Cmd + D (Ctrl + D) in the input to switch | The name automations use for a question, its [field key](https://formstep.io/docs/requests/field-keys/), is set in the editor's **Fields** tab. > **Adding help text** > > To explain a question, type a paragraph above or below it. A hidden title still names the column in exports. ## Choice options | Setting | Applies to | What it does | | - | - | - | | Options list | All choice types | The answers respondents pick from. Click to edit, drag to reorder | | Randomize order | Radio Buttons, Checkboxes, Dropdown, Ranking | Shuffles the options for each respondent to reduce bias | | Allow multiple / single selection | Single Picture Choice, Multi Picture Choice | Switches picture choice between one pick and several | With the cursor in an option, press Cmd + H (Ctrl + H) to hide it, or Cmd + D (Ctrl + D) to select it by default. For Star Rating and Linear Scale, change the maximum, up to 10, with **Increase** and **Decrease** in the block menu. ## Default values from other fields A default value can come from a hidden field or a calculated field instead of fixed text. This works on the six inputs that have a default value. 1. Click into the question's input. 2. Type `@` as the first character. A menu lists your hidden and calculated fields; keep typing to filter. 3. Pick a field. The input switches to default value mode on its own and shows the field as a chip, for example @recipient. To remove it, click the **×** on the chip. To type a literal `@`, press `Esc` when the menu opens. For example, a link ending in `?recipient=Jeff` fills a question that uses the `recipient` hidden field with “Jeff”. The respondent can change it. If the hidden field is empty, so is the question. A calculated field fills the question with its first value only; to show a value that keeps updating, use an [@ mention](https://formstep.io/docs/building-forms/answer-piping/). See [Hidden fields](https://formstep.io/docs/building-forms/hidden-fields/) and [Calculated fields](https://formstep.io/docs/building-forms/calculated-fields/). ## Validation rules Formstep checks answers as the respondent types, shows errors next to the question, and blocks submit until they are fixed. Email, Website URL, Phone Number, Number, Date Picker, and Time Picker check their format on their own. To add a rule, open the block menu and pick **Validation**. The rules depend on the question type: | Rule | Applies to | Effect | | - | - | - | | Min length / Max length | Text Input, Text Area, Website URL | Number of characters, not counting spaces at the ends | | Regex | Text Input, Text Area, Website URL, Email | The answer must match a pattern | | Minimum / Maximum | Number, Star Rating, Linear Scale | Limits the range | | Step | Number | The answer must be a multiple of the step; 1 means whole numbers only | | Earliest date / Latest date | Date Picker | Limits the dates | | Earliest time / Latest time | Time Picker | Limits the times | | Min selections / Max selections | Checkboxes, picture choice | How many options may be ticked | | Require all rows / Min answered rows | Matrix / Grid | How many rows must have an answer | | Min files, Max files, Max size, Accepted types | File Upload | See [File uploads & signatures](https://formstep.io/docs/building-forms/file-uploads-signatures/) | | Require email verification | Email (Business) | See [Respondent email verification](https://formstep.io/docs/building-forms/email-verification/) | Other question types have no rules besides required. A regex pattern describes the shape of an answer. For example, `^\d{5}$` accepts exactly five digits, such as a US ZIP code. An answer that does not match shows “Invalid value”, so say the expected format near the question. A pattern checks the format only: it cannot tell whether a card number or ID is real. ## Next steps [Question types](https://formstep.io/docs/building-forms/field-types/) — Every question you can add [Conditional logic](https://formstep.io/docs/building-forms/conditional-logic/) — Show, hide or require questions based on answers # Content blocks & formatting Add headings, text, images, videos, maps, embeds, and tables between your questions, format text with the toolbar, and show earlier answers with @. ## Available blocks Add a block from the **Blocks** tab, or type `/` on the canvas. To add a paragraph, start typing. | Block | Use it for | | - | - | | Heading 1 / 2 / 3 | Section titles and visual hierarchy | | Paragraph | Plain text: instructions, intros, disclaimers | | Bullet List / Numbered List | Lists, with formatting in each item | | Image | Logos, illustrations, and photos from your device. The block menu adds a caption, aligns, or replaces it | | Table | Prices, comparisons, or a schedule. See [Tables](#tables) | | 2 Columns | Blocks side by side. See [Form structure](https://formstep.io/docs/building-forms/form-structure/#columns) | ## Embeds Add an embed block, then open its block menu to set what it shows. | Block | How you set it | Use it for | | - | - | - | | YouTube Embed | Set URL: paste a YouTube URL or video ID | Explainer videos, tutorials, demos | | Google Maps Embed | Set location: enter an address or place name | Office, event venue, delivery area | | Embed anything | Set embed code: paste an embed snippet | Loom, Spotify, or any page that can be embedded | Large media makes the form slower to load. Add a video or map only when it helps the respondent. ## Text formatting ![Selected text on the canvas with the floating formatting toolbar above it](https://formstep.io/docs/images/building-forms/format-toolbar.png) Select text to see the formatting toolbar. Select text in a paragraph, heading, list item, or table cell to see the formatting toolbar. It has bold, italic, underline, strikethrough, inline code, equations, links, font, font size, and a highlight or underline color. The keyboard shortcuts are on [Understanding the editor](https://formstep.io/docs/building-forms/understanding-the-editor/#shortcuts). ## Tables A **Table** shows information such as prices or a schedule, and respondents can only read it. Click a cell to edit it. Hover a row or column and click its grip to add, duplicate, or delete a row or column. Drag the grip to move a row or column, and drag a border to resize. Cells take the same formatting as paragraphs, including `@` mentions. ## Earlier answers in your text Type `@` in text to show an earlier answer. For example, “Thanks, @First name” shows the respondent's name. See [@ mentions](https://formstep.io/docs/building-forms/answer-piping/). ## Next steps [@ mentions](https://formstep.io/docs/building-forms/answer-piping/) — Show earlier answers in your text [Form structure](https://formstep.io/docs/building-forms/form-structure/) — Pages, columns and moving blocks # File uploads & signatures Collect files and drawn signatures from respondents. Set accepted types, size caps, and file counts, and see how uploads count toward your storage. ## File upload Add **File Upload** from the Blocks tab, or type `/` and pick it. To set its rules, click the drag handle and open **Validation**: | Rule | What it does | | - | - | | Min files | How many files the respondent must attach. | | Max files | The most files allowed. Set it to 1 for a single file. | | Max size (MB) | The largest size per file. A single file can never be larger than 1 GB. | | Accepted types | Pick the file types you accept, such as PDF, Word, Excel, images, video, audio, or ZIP. Pick several to allow several. Leave it empty to accept any file. | The question shows respondents these rules, such as “Max 5 MB”. Open a submission in the [inbox](https://formstep.io/docs/submissions-analytics/submission-inbox/) to view or download its files. In logic, use **has file** and **has no file**. > **Uploads count toward your storage** > > Every file, signatures included, counts toward the workspace owner's storage. When it is full, the form refuses uploads. Set a Max size if you collect large files. See [Limits & quotas](https://formstep.io/docs/subscription-billing/limits-quotas/). ## Signatures A **Signature** question lets respondents draw a signature with a mouse or a finger. Formstep stores it as an image. In logic, use **is signed** and **is not signed**. Respondents cannot change what they signed: a form with a signature does not allow [edit after submit](https://formstep.io/docs/submissions-analytics/edit-after-submit/). ## Next steps [Documents block](https://formstep.io/docs/building-forms/documents-block/) — Give respondents files to read [Submission inbox](https://formstep.io/docs/submissions-analytics/submission-inbox/) — View and download each submission's files # Documents block Hand files to the respondent (a contract draft, a price list, a how-to) as a download list inside the form. ## What it is A Documents block is a list of files you upload, with a title. Every respondent sees the same list, and can open or download each file. The block asks no question. To collect files from respondents instead, use a [File Upload](https://formstep.io/docs/building-forms/file-uploads-signatures/) question. ## Add one 1. **Insert the block** Click Documents in the Blocks tab, or type / and pick it. Give it a title such as "Please read before you answer". 2. **Add your files** Click **Add document** or drop files onto the block. 3. **Name each row** Click a name to change it, for example "Lease contract draft" instead of "lease\_v3\_final.pdf". 4. **Publish** The files go live with the form. | What | Limit | | - | - | | File types | PDF and images | | Per document | 25 MB | | Per block | 100 MB, and 20 documents shown | > **Office documents** > > Word, Excel and PowerPoint files are not accepted yet. Export them as PDF first. ## Ask for confirmation To make respondents confirm they read the files, add a required **Toggle Switch** below the block, titled “I have read the lease contract”. For several statements, use Checkboxes. For a signature, add a Signature question. ## Check answers against the documents An [AI Validation](https://formstep.io/docs/building-forms/ai-validation/) below the block can read its documents and compare answers with them, as in “@Total amount matches the total on @Your invoice.” It reads the documents this respondent is shown, including the ones a request added. ## In submissions and exports Each submission records which documents the respondent was shown. You see the list in the submission, in exports, and in the PDF. ## Different files for each recipient A [request](https://formstep.io/docs/requests/overview/) created by an automation can add files for its one recipient, up to 100 MB. They show below your files, marked “For you”. Developers: see [Documents for one recipient](https://formstep.io/docs/developers/requests-api/#documents). ## Next steps [AI Validation](https://formstep.io/docs/building-forms/ai-validation/) — Check answers against the documents [File uploads & signatures](https://formstep.io/docs/building-forms/file-uploads-signatures/) — Collect files and signatures from respondents # Payment collection Accept one-time payments from respondents via Stripe. Connect your account, set a price or change it with logic, and find each payment with its submission. ## Connect Stripe Connect a Stripe account once, and every form in the workspace can charge through it. A person has to do this; an AI agent or workflow cannot. 1. **Open Integrations** In the sidebar, open **Integrations** and find **Stripe** under Payments & scheduling. Or click **Connect** on a Payment question. 2. **Click Connect** Stripe opens to approve the connection. You can create a Stripe account there if you have none. 3. **Complete Stripe onboarding** Stripe asks for your identity, bank account, and business details, then sends you back to Formstep. Payments go straight to your Stripe account, and receipts show your business name. You pay Stripe's usual fees; Formstep adds none. Card details never pass through Formstep. The badge on the Payment question shows whether the account can take payments. Connected means it can. Verifying means Stripe is reviewing the account, which usually needs nothing from you. For Setup incomplete or Restricted, open your Stripe Dashboard; for Disconnected, reconnect. ### Several Stripe accounts The first account is the workspace default. To use another account, connect it too, then click **Make default** (owner only), or pick it under **Stripe account** on one form's Payment question. Disconnecting an account cancels open payments on its forms, and they stop taking payments until they have another account. Completed payments stay in your Stripe account. ## Set the price Pick a currency and type an amount on the Payment question. Stripe has a minimum per currency, such as $0.50 for USD and EUR or £0.30 for GBP. The Payment question takes one-time charges only, not subscriptions. Supported currencies: USD, EUR, GBP, JPY, CAD, AUD, CHF, SEK, NOK, DKK, NZD, SGD, HKD, AED, TRY. > **Publishing waits for a complete setup** > > A form with a Payment question publishes only when each one has a currency and a valid amount, and the Stripe account can take payments. ## Change the price with logic To change the price based on answers, add a logic rule with the **Set payment amount** action. It can **set** the price, or **increase** or **decrease** it, by a fixed number or by a [calculated field](https://formstep.io/docs/building-forms/calculated-fields/). Rules start from the price on the question and run top to bottom. Example: the question says $50. A “Rush delivery” switch increases it by 15, and a “Member” switch decreases it by 5. With both on, the respondent pays $60. Logic can also read a Payment question: **amount equals**, **is greater than**, **is less than**, **is completed**, and **is not completed**. ## What respondents see The respondent clicks **Complete Payment**, pays in Stripe Checkout, and comes back to submit the form. They cannot change the amount. If a payment fails, **Retry payment** opens Checkout again. After payment, the answers that set the price are locked, so the respondent cannot change what they paid for. A form with a Payment question does not allow [edit after submit](https://formstep.io/docs/submissions-analytics/edit-after-submit/). ## Find payments Each payment shows with its submission in the List view on the form's Submissions tab: its status (pending, paid, failed, or canceled), the amount, and a link to the Stripe receipt. Emails, PDFs, exports, and integrations include the payment too. Handle [refunds](https://docs.stripe.com/refunds) and [disputes](https://docs.stripe.com/disputes) in your Stripe Dashboard: you are the merchant. The submission then shows Refunded, Partially refunded, or Disputed. Formstep may refund payments in cases of abuse. ## Next steps [Conditional logic](https://formstep.io/docs/building-forms/conditional-logic/) — The rules that set the payment amount [Calculated fields](https://formstep.io/docs/building-forms/calculated-fields/) — Work out a total to charge # Respondent email verification On the Business plan, respondents confirm their email address with a 6-digit code before they can submit, so every address you collect is real and reachable. This needs the Business plan. [Compare plans](https://formstep.io/pricing/) ## What it does Turn it on for an **Email** question, and the respondent confirms the address with a 6-digit code that Formstep emails them. They cannot submit until the code is right, so you do not receive fake or mistyped addresses. ## Turn it on 1. **Open the email question's block menu** Click the drag handle next to the Email question and open **Validation**. 2. **Turn on Require email verification** The switch is under **Verification**. 3. **Publish** The published form now asks respondents to verify before they can submit. It is not available inside [repeating groups](https://formstep.io/docs/building-forms/repeating-groups/). ## What respondents see 1. When the respondent types a correctly formatted address, a **Verify email** action appears under the input. 2. Formstep emails a 6-digit code to that address. The code expires after 10 minutes. 3. The respondent enters the code and the question shows a green Verified pill. **Resend** works after 30 seconds. After five wrong codes, the respondent has to ask for a new one. Temporary email addresses are refused. Codes come from your [custom email domain](https://formstep.io/docs/branding-domains/custom-domains/#custom-email-domains) if you set one. If two email questions hold the same address, one code covers both. A changed address needs a new code. A verified address cannot be changed later, even where [edit after submit](https://formstep.io/docs/submissions-analytics/edit-after-submit/) is on. > **Verifying an answer vs. requiring sign-in** > > This rule verifies an email _answer_. To require respondents to sign in before they can open the form at all, use the [authentication gate](https://formstep.io/docs/sharing-publishing/authentication-gate/) instead. ## Next steps [Authentication gate](https://formstep.io/docs/sharing-publishing/authentication-gate/) — Ask people to sign in before they open the form [Validation rules](https://formstep.io/docs/building-forms/field-configuration/#validation-rules) — Every rule a question can check # Repeating groups Let a respondent fill in the same questions as many times as they need, such as one name and age per guest, and see where the answers show up. ## What a repeating group is A repeating group holds one or more questions. Respondents add as many items as they need, each with its own answers. Use it for event guests (a name and age per person), order lines (product, quantity, and price), team contacts, or work history. ## Add a repeating group 1. **Insert the group** Click **Repeating Group** in the Blocks tab, or type / and pick it. 2. **Name the group** Give it a title, such as "Additional guests". 3. **Add questions inside** Add questions inside the group, such as Text Input, Number, or Dropdown. 4. **Edit the add-button label** Change "Add one more" to fit, such as "Add another guest". To group questions you already have, select them and pick **Create repeating group** in the block menu. **Remove grouping** undoes it. A group cannot go inside another group or a column. Payment, Schedule appointment, Documents, and email verification do not work inside a group. ## What respondents see The first item shows like any other part of the form. With two or more, each item becomes a numbered card with an X to remove it. A group holds up to 100 items. Required questions are required in every item. On a [request](https://formstep.io/docs/requests/overview/) that locks a question in the group, the recipient cannot add or remove items. ## Groups and conditional logic [Logic](https://formstep.io/docs/building-forms/conditional-logic/) can show or hide the whole group, but cannot read or target the questions inside it. ## Where the answers show up - In the [submission inbox](https://formstep.io/docs/submissions-analytics/submission-inbox/), as numbered cards, one per item. - In the [question summary](https://formstep.io/docs/submissions-analytics/question-summary/), as one card per group, with counts per question. - In [exports](https://formstep.io/docs/submissions-analytics/exports/), as one column for the whole group, with items separated by |. ## Use group answers in text An @ mention of a question in the group lists its answers from every item. A mention of the whole group lists all items. See [@ mentions](https://formstep.io/docs/building-forms/answer-piping/#referencing-repeating-groups). ## Next steps [Mention a repeating group](https://formstep.io/docs/building-forms/answer-piping/#referencing-repeating-groups) — Show a group's answers in your text [Conditional logic](https://formstep.io/docs/building-forms/conditional-logic/) — Show or hide the whole group # Conditional logic Show, hide, require, and jump to pages based on answers, or calculate values and stop a submission. Write a rule, combine conditions, and test it in Preview. ## What logic can do A logic block holds one or more rules. Each rule reads **When** these conditions are true, **Do** these actions. | Action | What it does | | - | - | | Show / Hide | Shows or hides any block: a question, a paragraph, an image, or one option of a choice question. | | Make required / Make optional | Requires a question on some paths only, or relaxes it on others. | | Set next page | Skips to a page, so different answers lead down different paths. | | Calculate value | Works out a total, score, or price in a [calculated field](https://formstep.io/docs/building-forms/calculated-fields/). | | Set payment amount | Sets, raises, or lowers the price of a [Payment question](https://formstep.io/docs/building-forms/payment-collection/#dynamic-pricing). | | Prevent navigation to next page | Stops the respondent from going on, with an optional message saying why. | | Prevent form submission | Stops submission until conditions are met, such as terms accepted, with an optional message saying why. | ## Add a logic block A logic block sits on the canvas between questions, like any other block. 1. **Add a Logic block** Click **Logic** in the Blocks tab, or type `/logic` on the canvas. 2. **Set the conditions** Pick a question, an operator, and a value. **Add condition** adds another row. 3. **Choose actions** Pick an action, such as Show or Set next page, and what it acts on: a question, block, option, or page. 4. **Add more rules (optional)** One logic block can hold several rules, each with its own conditions and actions. To show a block only sometimes, hide it with **Hide** in its block menu, then add a rule that shows it. The **Logic** tab of the side panel lists every rule in plain words, such as _When Plan is Pro, show VAT number_. Click a rule to jump to it. **Needs attention** shows rules that point at a deleted question. ## Conditions A condition can read questions, hidden fields, calculated fields, and the respondent's language. With two or more conditions, click the **And** or **Or** pill to switch: with **And** all must be true, with **Or** one is enough. **Add a set of conditions** adds a group with its own And/Or, for rules like “plan is Pro _and_ (country is Norway _or_ Sweden)”. A set cannot hold another set. ![A logic block whose When section joins Plan is Pro and a set headed Any of these, holding Country is Norway and Country is Sweden, with an And pill on the outer bracket and an Or pill inside the set, and a Do action that shows the VAT number question.](https://formstep.io/docs/images/building-forms/logic-block.png) Plan is Pro and (country is Norway or Sweden): an And rule with an Or set inside. ### Operators The operators depend on the question. The main ones are below; most also have an opposite, such as **is not empty**. Text comparisons ignore capital letters and spaces at either end. - Text, email, web address, and phone: is, is not, contains, starts with, ends with, is empty. - Numbers, ratings, and scales: is, is greater than, is less than (or equal to), is between, is empty. - Dates and times: is, is before, is after, is between. - Radio Buttons and Dropdown: is, is not. Checkboxes and picture choice: has checked any of, all of, or exactly. - Others: Toggle Switch is on or off, Signature is signed, File Upload has file, Payment is completed, Ranking option is ranked first, Matrix row answer is. Logic can show or hide **Schedule appointment** and **Documents**, but cannot read them. See [Cal.com scheduling with logic](https://formstep.io/docs/integrations/cal-com/#logic). > **A hidden question has no value** > > When logic hides a question, its answer is ignored in conditions, calculations, and the payment amount, and is not saved. The same goes for questions on a skipped page. A hidden question is never required. Hidden _fields_ always keep their value. ## When rules disagree When two matching rules disagree, for example one shows a block and another hides it, the first rule in the form wins, and the editor flags the rule that never applies. To show a block for several answers, one rule with conditions joined by **Or** is easier to read than several rules. ## Common patterns ### Show a follow-up question After a 1 to 5 Star Rating, a rule says: when rating **is less than or equal to** 3, **Show** “What could we improve?”. ### Branch to different pages An event registration form asks: “Are you attending in person or remotely?” Three rules follow: - When attendance **is** “In person”, **Set next page** “Venue details”. - When attendance **is** “Remote”, **Set next page** “Streaming setup”. - When arrival time **is not empty**, **Set next page** “Confirmation”. Arrival time is a required question on “Venue details”. A jump happens when the respondent clicks Next on the page with the question it reads. The third rule lets in-person attendees skip “Streaming setup”, so both paths end on “Confirmation”. ### Calculate a score and branch on it Three Star Ratings add up into a calculated field, “Lead score”, with **Calculate value**. A second rule: when Lead score **is greater than** 12, **Set next page** “Priority follow-up”. ### Stop submission below a total When Order total **is less than** 10, **Prevent form submission**. Type the reason in **Message shown to respondent**, such as “Your order must total at least 10”, so they know what to change. > **Test logic with Preview** > > **Preview** runs your logic. Fill in the form there to check every branch before you publish. ## Next steps [Calculated fields](https://formstep.io/docs/building-forms/calculated-fields/) — Work out a total, score or price [Form structure](https://formstep.io/docs/building-forms/form-structure/) — Split a form into pages to jump between # Hidden fields Pass data the respondent never sees into a form, from the address of a share link or the context of a request, and save it with each submission. ## What they are for - Track where answers come from, such as an email campaign or a social media post. - Link answers to your systems with a contact, deal, or order ID. - Change the form with [logic](https://formstep.io/docs/building-forms/conditional-logic/), for example show extra questions for `plan=enterprise`. - Check answers against what you know with [AI Validation](https://formstep.io/docs/building-forms/ai-validation/), for example an order number an uploaded invoice must mention. ## Add a hidden field Click **Hidden Field** in the Blocks tab, or type `/` and pick it. It cannot go inside a column or a repeating group. Then set: - **Field type**: text (the default) or number. - **Parameter name**: the URL parameter that fills it, such as `utm_source` or `contact_id`. It is also the field's [field key](https://formstep.io/docs/requests/field-keys/). ## Fill it from the URL When someone opens a share link with a matching parameter, the hidden field takes its value: ``` https://form.formstep.io/abc123?utm_source=newsletter&contact_id=12345 ``` - Values are read once, when the form opens. - A number hidden field ignores values that are not numbers, so `?score=abc` leaves it empty. - To add parameters to a share link without editing the URL, use the link's [Query parameters](https://formstep.io/docs/sharing-publishing/sharing-embedding/#query-parameters). UTM tags and campaign tracking are explained there too. To show a URL value in a question the respondent can edit, such as their name, see [Default values from other fields](https://formstep.io/docs/building-forms/field-configuration/#default-values). ## On a request Request links ignore URL parameters, so the recipient cannot change your values. A request fills hidden fields from its context, and can fill visible questions directly. See [Prefill, locked fields and hidden fields](https://formstep.io/docs/requests/creating-requests/#three-buckets). ## Where values show up Hidden fields have their own columns in the Submissions table, marked with an eye-slash icon. They also appear in exports and integrations. ## Next steps [Query parameters](https://formstep.io/docs/sharing-publishing/sharing-embedding/#query-parameters) — Add values to a share link without editing the URL [Prefill, locked fields and hidden fields](https://formstep.io/docs/requests/creating-requests/#three-buckets) — Fill hidden fields from a request # Calculated fields Work out scores, totals, and text from form answers. Give a calculated field a starting value, then change it with logic rules as respondents answer. ## How it works A calculated field holds a number or a text. It starts at an initial value, and [logic rules](https://formstep.io/docs/building-forms/conditional-logic/) with the **Calculate value** action change it, for example add 10 when an answer is Yes. ## Add a calculated field Click **Calculated Field** in the Blocks tab, or type `/` and pick it. It cannot go inside a column or a repeating group. Then set: - **Field type**: number or text. - **Name**: used in logic and @ mentions, and as its field key in integrations. - **Initial value**: a value such as `0`, or a [hidden field](https://formstep.io/docs/building-forms/hidden-fields/) of the same type. ## Operations | Field type | Operations | | - | - | | Number | add, subtract, multiply, divide, set to, reset to initial | | Text | set to, append text, prepend text, reset to initial | Each operation takes a fixed value, such as 10, or the value of another question or field. Answers to questions hidden by logic do not count. Every matching rule runs, top to bottom, on the running value. ## Examples For a quiz score, “Total score” starts at 0. One rule adds 10 when Q1 is “Yes”, and another adds the value of Q2 when Q2 is greater than 5. For a price, one rule sets Price to 10 when size is “Small”, and another sets it to 20 when size is “Large”. A last rule with no conditions multiplies Price by the quantity, since a rule with no conditions always runs. To charge this price, see [Change the price with logic](https://formstep.io/docs/building-forms/payment-collection/#dynamic-pricing). For a full name, a text field appends @First name, a space, and @Last name. Logic can also read a calculated field, for example “when Total is greater than 50, go to the Premium page”. To show the value to the respondent, use an [@ mention](https://formstep.io/docs/building-forms/answer-piping/). To start a question with it, see [Default values from other fields](https://formstep.io/docs/building-forms/field-configuration/#default-values). ## Next steps [Conditional logic](https://formstep.io/docs/building-forms/conditional-logic/) — The rules that change a calculated field [Change the price with logic](https://formstep.io/docs/building-forms/payment-collection/#dynamic-pricing) — Charge the price your form works out # @ mentions Type @ to show an answer somewhere else in your form, from question titles to the thank-you page and emails. Each respondent sees their own values. ## How it works Type `@` and pick a question or field. It shows as a chip, such as @First name, and the respondent sees their answer in its place. To type a plain `@`, press `Esc`. ## Where mentions work - Question titles. A question cannot mention its own answer. - Paragraphs, headings, list items, table cells, links, and image captions - The thank-you page - Translations of all of these, where the mention carries into each language - Email subjects and bodies: notifications, confirmations, reminders, and the request invitation - Messages and field mappings in integrations Option labels can mention only hidden fields, calculated fields, and metadata. Button labels and repeating group titles take no mentions. A mention shows a value as text. To put a value inside a question, where the respondent can edit it, see [Default values from other fields](https://formstep.io/docs/building-forms/field-configuration/#default-values). ## What you can mention The @ menu lists your questions, [hidden fields](https://formstep.io/docs/building-forms/hidden-fields/), [calculated fields](https://formstep.io/docs/building-forms/calculated-fields/), repeating groups, and metadata about the submission. The metadata you will use most: | Variable | What it shows | | - | - | | 4 or 6 Digit PIN, 4 or 6 Char Code | A number or code unique to the submission, in the form and in emails | | Form Name | The form’s name, in emails | | Submitted At | When the response was submitted, in emails | | PDF Link | A link to the submission PDF, in emails | > **PINs as reference numbers** > > Put a **6 Digit PIN** in the confirmation email as a reference number. You can search for it in the submissions table. ## Mention a repeating group A [repeating group](https://formstep.io/docs/building-forms/repeating-groups/) can have many items: - A mention of one question in the group joins its answers from every item with a middle dot (·). The Name question of three guests shows “Jane Appleseed · Marcus Lee · Priya Patel”. - A mention of the whole group shows every item, with commas between items. A group with Name and Age shows “Jane Appleseed · 34, Marcus Lee · 29”. ## Delete a mentioned question A mention of a deleted question or field blocks publishing. The issue indicator lists each one, in the form, emails, [translations](https://formstep.io/docs/building-forms/translating-form-content/#field-reference-checks), and integrations. Remove the chip and insert the right one. A hidden or calculated field added back under the same name reconnects on its own. ## Next steps [Hidden fields](https://formstep.io/docs/building-forms/hidden-fields/) — Pass in data the respondent never sees [Calculated fields](https://formstep.io/docs/building-forms/calculated-fields/) — Work out a score, a total or a full name # AI Validation Check answers and uploaded files against a rule you write in one sentence. Respondents see a note when something does not fit, and you see every result. This needs the Pro plan or higher. [Compare plans](https://formstep.io/pricing/) ## What it does An AI Validation is a block under the questions it checks. You write a rule in one sentence and name the answers it reads with `@`. It catches mistakes a normal validation rule cannot: a document that is not what you asked for, two answers that contradict each other, or an answer that is filled in but makes no sense. - The respondent sees a note under the question when something does not fit. They can fix the answer or submit anyway, because a check only warns and never blocks the submission. - You see the result on every submission: Passed, or Needs review with a short reason written for you. Automations get the same result. ## What you can check | What you want | A rule that checks it | | - | - | | The right document | @Invoice is an invoice for order @order\_number, dated in 2026. | | Names that match | The name on @Passport matches @Full name, and the passport has not expired. | | Two answers that agree | @Work email uses the same domain as @Company website. | | A plausible answer | @Company website is the website of a real company, not a social media profile. | | The right kind of file | @Bank statement is a bank statement from the last three months, not a screenshot of an app. | | Enough detail | @Damage description says what was damaged, where and when. | | Answers that match a document you sent | @Total amount matches the total on @Your invoice. | A rule can name a [hidden field](https://formstep.io/docs/building-forms/hidden-fields/) as the value to compare against, such as an order number or a customer name your system already knows. In the first example, `@order_number` comes from the request, and the respondent never sees it. A rule can also name a [Documents block](https://formstep.io/docs/building-forms/documents-block/) above it, to check answers against the files you hand the respondent, including the ones a request adds. In the last example, the check reads the invoice in the block and compares it with the total the respondent entered. A rule that names a Documents block still needs at least one answer, and it does not run while the block shows no document. ## Add an AI Validation 1. **Insert the block** Under the question you want to check, click **AI Validation** in the Blocks tab, or type `/ai`. The block starts out naming the question above it. It cannot go inside a column or a repeating group. 2. **Write the rule** Write one sentence that says what a good answer looks like. Type @ to name questions and Documents blocks above the block, and hidden fields. The check reads only what you name. 3. **Try it in Preview** Open Preview and answer as a respondent would. Preview runs the real check, so you see the note respondents will see. ![A Company website question in the editor with an AI Validation block under it reading: Company website is the website of a real company, not a social media profile.](https://formstep.io/docs/images/building-forms/ai-validation-block.png) An AI Validation under the question it checks. Respondents see only the note it shows. ![Typing @ inside an AI Validation opens a picker listing the questions above it under Answers and the hidden fields under Hidden Fields.](https://formstep.io/docs/images/building-forms/ai-validation-mention.png) Type @ to name what the rule reads: questions and Documents blocks above the block, and hidden fields. > **A rule that names nothing never runs** > > If the sentence names no answer the check can read, with no `@` or only questions below the block, the block says so: “Name a question or file above with @, or this check never runs.” ## Write a good rule - Check one idea per block. To check two things, add two blocks. Each gets its own note and its own result. - Be concrete about names, numbers, dates and document types: “dated in 2026” checks better than “recent”. - Name every answer the rule needs. The check reads only the answers you name with `@`. - Write in any language. The respondent's note follows the language the form is shown in. ## What respondents see The check runs once the respondent has filled in the answers it names and moved on. “Checking…” shows while it runs. Next and Submit wait up to 8 seconds for it. ![A Company website answer of instagram.com/maersk with a note under it saying a company website is needed rather than an Instagram profile, marked Checked automatically.](https://formstep.io/docs/images/building-forms/ai-validation-note.png) The note sits under the answer it is about, written for the respondent in the form's language. - The note is written for the respondent, in the language the form is shown in. It says what is missing and how to fix it. It never mentions AI or your rule, and never shows a hidden field's value. It says “the order number we have for you” instead. - When the answers fit, a quiet “Looks good.” shows instead. - Changing an answer checks again. The same answers are never checked, or charged for, twice: coming back to a saved draft shows the earlier result. - A check that cannot run shows nothing. Without a plan or enough credits, or when the check fails, the respondent carries on as if there were no check. ## Results on each submission Every submission records each check as it stood on the submitted answers. On the [request page](https://formstep.io/docs/requests/managing-requests/#request-page), and in a submission's details on the Submissions tab, each check sits right after the last answer it read, with an icon for its result: | Result | What it means | | - | - | | Passed (green check) | The answers fit the rule. | | Needs review (amber warning) | Something did not fit. The reason, written for you in the form’s language, says what. | | Not checked (dashed circle) | The check did not run on these answers: no plan or credits, it was still running at submit, an answer it names was empty, or a Documents block it names had no document it could read. | ![A request's answers: Company website passed its AI Validation, and the Work email check needs review because the email uses gmail.com instead of the company's domain.](https://formstep.io/docs/images/requests/request-page-ai-validation.png) Each check right after the answers it read: what it found, then the rule it checked. An automation gets the same results with the answers, so it can send flagged submissions to a person to review. Developers: see [Webhooks reference](https://formstep.io/docs/developers/webhooks-reference/#payload). ## Files it can read A check reads PDF, Word, Excel, CSV, plain text, Markdown and HTML documents, and JPEG, PNG, GIF and WebP images: up to five files per check, each up to 4.5 MB. When a file cannot be read (another type, too large, or damaged), the note asks the respondent for a clearer photo or the original file. Documents from a Documents block count toward the same limits. When the check cannot read one of them, it does not run, because the respondent cannot fix a file you sent. The submission records Not checked instead. ## Plans and AI credits AI Validation is on the Pro and Business plans. On Free you can add the block, but checks do not run. Each check uses the workspace owner's AI credits, including the checks any member runs in Preview. | A check that reads | Credits it uses | | - | - | | Text answers | Under 1 | | A photo, or a one-page PDF | \~1 | | Each further PDF page | \~0.4 | When the owner's credits run out, checks stop until the owner adds more. Respondents notice nothing, the submission records Not checked, and the editor and an email tell the owner. See [AI credits & usage](https://formstep.io/docs/ai/ai-credits-usage/). ## What is sent, and what is kept A check sends your rule, the answers it names and the files it reads to the AI model, and nothing else from the form. The model only returns a result and a note. It cannot act on anything written in an answer or a file, so the worst a planted instruction can do is change a check's result, which you see on the submission. The result is stored with the submission. The note a respondent saw is kept with their link for up to 30 days, so the same answers are not checked again, and is deleted with the submission, the request or the form. ## Next steps [Documents block](https://formstep.io/docs/building-forms/documents-block/) — Give respondents files a check can compare answers with [Hidden fields](https://formstep.io/docs/building-forms/hidden-fields/) — Values the respondent never sees, such as an order number [AI credits & usage](https://formstep.io/docs/ai/ai-credits-usage/) — What a check costs and how to buy credits [The Requests page](https://formstep.io/docs/requests/managing-requests/) — See each check's result on a request # Thank-you page & redirect Customize the thank-you page respondents see after they submit, with text, links, images, and their answers, or send them to your own web page. ## Add a thank-you page Without one, respondents see “Thanks for your response!” To make your own, click **Thank You Page** in the Blocks tab, or type `/` and pick it. A green **Thank You Page** divider appears at the end of the form, and everything below it is the thank-you page. To go back to the default, click the trash icon on the divider. A form has one thank-you page, and it is always last. It can hold: - Headings, paragraphs, lists, and tables - Images, YouTube videos, Google Maps, and embeds - Links, including link buttons to your site, store, or calendar - Columns, to lay any of the above out side by side - Earlier answers: type `@` to mention one, as in “Thanks @name, we'll review your application”. See [@ mentions](https://formstep.io/docs/building-forms/answer-piping/). Questions, logic, and page breaks cannot go on it. The thank-you page is translated with the rest of the form in **Translate**. See [Multilingual forms](https://formstep.io/docs/building-forms/translating-form-content/). > **Show the next step** > > Respondents are still paying attention right after they submit, so use the thank-you page to point them to the next step, such as booking a call or reading a confirmation. ## Send respondents to your own page 1. Open **Settings**, then the **Submissions** tab. 2. Under **After someone submits**, pick **Go to a web page**. 3. Type the address in **Web page**. `yoursite.com/welcome` becomes `https://yoursite.com/welcome`. After submitting, the respondent sees “Redirecting in 3 seconds…”, and then the browser opens your page. If you built a thank-you page, it shows during those 3 seconds. Under **After someone submits** you pick one option: go to a web page, offer another response, or [show live results](https://formstep.io/docs/building-forms/live-results/). A form that redirects shows no **Submit again** button and no results. ### Pass answers to your page Under **Redirect URL query parameters**, add a row per parameter: a name, and the question or field whose value it sends. You can send answers, hidden fields, calculated fields, and details such as the submission ID. A preview shows the finished address: ``` https://yoursite.com/welcome?email=alice%40example.com&plan=pro ``` Use it to greet respondents by name on your page, or to fill in a form on your site. Empty answers are left out. A question inside a [repeating group](https://formstep.io/docs/building-forms/repeating-groups/) sends one parameter per item: `?guest=Alice&guest=Bob`. ## Next steps [Live results](https://formstep.io/docs/building-forms/live-results/) — Show how everyone answered after a submit [@ mentions](https://formstep.io/docs/building-forms/answer-piping/) — Greet respondents with their own answers # Live results Show respondents the answer counts after they submit, put the results on a big screen, and add an AI summary of what they mean. ## Turn it on 1. Open **Settings**, then the **Submissions** tab. 2. Under **After someone submits**, pick **Show live results**. This replaces the other choices there, such as going to a web page. 3. Save. The next respondent sees the results after they submit. The **Live results link** under the choices is the address of the results page. Click it to copy it. Publish the form first: the link appears once the form has a share link. ![After someone submits with four cards and Show live results picked; under them the Live results link, then the AI summary switch with what the summary is based on and a link to the Summary tab](https://formstep.io/docs/images/building-forms/settings-live-results.png) Settings, Submissions tab, with Show live results picked and the AI summary shown. ## What respondents see After they submit, respondents see the thank-you page with “Showing results in 3 seconds…”, then the results. Someone who comes back to the form after submitting goes straight to the results. If the form lets people [change their answers](https://formstep.io/docs/submissions-analytics/edit-after-submit/), the **Edit your response** button stays under the results. ![Live results under the form's cover and logo: the AI summary card with a headline and three points, then a star rating with its bars and a linear scale as columns with the average marked](https://formstep.io/docs/images/building-forms/live-results.png) The results after a submit, and on the results page. Each choice, switch, rating, scale, ranking, and matrix question gets a chart with its answer counts, in your form's button color. Text, email, phone, contact, file, signature, and payment questions never appear, and no result shows a name or a written answer. ## AI summary An AI summary puts a short recap above the results: a headline and up to three points about what the answers add up to. Your team generates it on the form's [Summary](https://formstep.io/docs/submissions-analytics/question-summary/) view, which explains what it reads and what it costs. To show it to respondents too: 1. Generate a summary on the Summary view and read it there. 2. In **Settings**, the **Submissions** tab, with **Show live results** picked, switch **AI summary** on. 3. Save. Visitors now see it at the top of the results. The summary does not update on its own. The card says how many responses it is based on and when it was generated, so visitors know it can trail the live counts. Switching **AI summary** off hides it from respondents; your team still sees it on the Summary view. ## The results page Every share link has a results page: the link followed by `/results`, such as `form.formstep.io/aB3dE9xY/results`. Anyone with it sees the results without answering, and every share link of a form opens the same results. Open it on a projector or a TV while people answer: new responses appear within a few seconds, and on a wide screen the results sit in two columns with larger text. ## What counts - Completed responses from the form's share links. Answers to [requests](https://formstep.io/docs/requests/overview/) stay with the request and are not counted. - The latest 5,000 responses. Older ones are left out, and the page says so. - The published form: a question you add or rename shows after you publish. - Deleting a response takes it out of the results. ## Privacy and access - A form behind a [password](https://formstep.io/docs/sharing-publishing/authentication-gate/#password-protect) keeps its results behind it: respondents who entered the password see them after they submit, and the results page does not open. - A closed form keeps its results page. A deleted form or a revoked link takes it away. - Pick another choice under After someone submits, and the results page closes. - Questions and options show as written, in the form's language. ## Next steps [Thank-you page & redirect](https://formstep.io/docs/building-forms/custom-thank-you/) — The other choices after someone submits [Aggregated summary](https://formstep.io/docs/submissions-analytics/question-summary/) — Where your team generates the AI summary # Multilingual forms Set the language you write a form in, translate questions, text, and emails by hand or with AI, and control which language each respondent sees. ## The form's language Set it under **Form language** in **Settings**, **General**. It is the language you write the form in, and it sets the language of Formstep's own buttons, error messages, default emails, and date and number formats. Changing it does not translate your questions or text: for that, add a language below. Formstep supports 65 languages, from Afrikaans to Vietnamese, including regional ones such as British English, Mexican Spanish, Canadian French, and Brazilian Portuguese. Type a name or code in the picker to find one. Forms and PDFs read right to left in Arabic, Hebrew, Persian, Urdu, and other right-to-left languages. ## Translate your form The translation editor is on every plan. In the editor, click **Translate** in the top bar. Each row shows the **Original** and its **Translation**, with a status. The **Form content** and **Emails** tabs keep form text and email text apart. Changes save as you type. ![The translation editor with the original text on the left and the translation on the right](https://formstep.io/docs/images/building-forms/translate-editor.png) Translate form content and email text side by side. 1. Click **Add language** and pick a language. 2. Type each translation, or let AI suggest one. 3. Publish the form. Respondents see a language only after you publish. ### What you can translate - Headings, paragraphs, lists, and tables - Question titles, placeholders, options, and scale and matrix labels - Image captions and the thank-you page - Your own text for the confirmation, reminder, and request invitation emails Answers, files, and Cal.com event names are not translated. Keep `@` mentions in the translation, and translate only the text around them. ## AI translation Use AI for a first draft, then review it. Each row has a **Suggest** button, and the buttons above the rows act on many rows at once, such as **Translate everything**, **Refresh outdated**, or **Polish translations**. AI suggestions use [AI credits](https://formstep.io/docs/ai/ai-credits-usage/). AI fills the rows as suggestions. Accept or decline each one, or use **Accept all**. Have a native speaker check legal and payment text before you publish. ## Translation statuses | Status | What it means | What to do | | - | - | - | | Up to date | The translation matches the current text. | Nothing. | | Not translated | No translation yet. | Translate it, or ask AI. | | Outdated | You changed the original after translating it. | Click "Show previous version" to compare, then update it. | | AI suggested — review | AI drafted it, and you have not accepted it yet. | Accept or decline it. | | Invalid reference | The text mentions a question that no longer exists. | Fix the mention, or click "Mark as valid". | Each language shows how many rows are complete, missing, and outdated. ## Publish a language Rows you have not translated show in the original language, so you can publish before everything is done, but the form will mix languages. The issue indicator warns about missing and outdated rows. If a translation mentions a question that no longer exists, the row shows a warning and publishing stops until you fix it. ## Which language respondents see Once a translation is published, respondents first see “Pick your language to start”. They skip it when their link sets the language, or when they picked one on this form before in the same browser. They can switch to another published language at any time. To choose the language for them, send a link ending in `?lang=` and the language code, such as `?lang=fr` for French, or set a default language on a [share link](https://formstep.io/docs/sharing-publishing/sharing-embedding/). A request opens in the request's language. A language you have not published is ignored. > **Check a language before you share it** > > Open the form with `?lang=` and the language code, and send a test response. ## Emails and PDFs - Emails to respondents use your translated text in their language. Without a translation, Formstep sends its own default email in that language instead of your original text. - PDFs show the form in the language the respondent used, as it was when they submitted. For a guide to the whole process, from translating a form to reading the answers in your language, see [how to make a multilingual form](https://formstep.io/blog/multilingual-forms/). ## Next steps [Use AI chat](https://formstep.io/docs/ai/using-ai/) — Translate and polish with AI chat [Form settings](https://formstep.io/docs/building-forms/form-settings/) — Set the form's language under General # Form settings Choose who can open a form, what happens after someone submits, and which emails go out, all in the Settings dialog. ![The Settings dialog on its General tab, with the tabs General, Public link access, Submissions, Emails, and Danger zone down the side and an Integrations row below them](https://formstep.io/docs/images/building-forms/settings-dialog.png) Settings, General tab. Click Save changes to apply. ## General | Setting | What it does | | - | - | | Form name | The name in your workspace. Respondents do not see it | | Form language | The language of buttons and messages. It does not translate your questions. See [Multilingual forms](https://formstep.io/docs/building-forms/translating-form-content/#source-language) | | Show Formstep branding | Turning it off needs Pro or Business | ## Public link access These settings decide who can open the form through the Public link. Requests skip them, because each request link belongs to one person. - **Require sign-in** and **Password**: see [Authentication gate](https://formstep.io/docs/sharing-publishing/authentication-gate/). - **Spam protection**: a Cloudflare Turnstile check before each submission. Always on for Free forms; on Pro and Business you can turn it off. See [Bot protection](https://formstep.io/docs/sharing-publishing/captcha-bot-protection/). ## Submissions ![The Submissions tab: Accepting submissions with Open, Until a date, and Closed; Delete submissions with Never, On a date, and After; After someone submits with four cards; and Let people change their answers](https://formstep.io/docs/images/building-forms/settings-submissions.png) Settings, Submissions tab. | Setting | What it does | More | | - | - | - | | Accepting submissions | Open, Until a date, or Closed | [Scheduled close](https://formstep.io/docs/sharing-publishing/scheduled-close/) | | Delete submissions | Delete answers on a date, or after a number of days. Setting a rule needs Business | [Submission retention](https://formstep.io/docs/submissions-analytics/submission-retention/) | | After someone submits | Show thank-you page, Show live results, Offer another response, or Go to a web page | [Thank-you page & redirect](https://formstep.io/docs/building-forms/custom-thank-you/), [Live results](https://formstep.io/docs/building-forms/live-results/) | | AI summary | With live results on: show the AI summary your team generates on the Summary view above the results | [AI summary](https://formstep.io/docs/building-forms/live-results/#ai-summary) | | Let people change their answers | An edit link after submit | [Edit after submit](https://formstep.io/docs/submissions-analytics/edit-after-submit/) | Settings marked Public link only do not apply to requests. ### Drafts Formstep saves answers as the respondent goes, and restores them when they come back. On Free, drafts from the Public link stay in the respondent's browser. On Pro and Business, they are saved on the server, so a reminder email can bring the respondent back on any device. Request drafts are always saved on the server. ## Emails Each email has its own card with a switch, a **Preview**, and a subject and body you can change with [@ mentions](https://formstep.io/docs/building-forms/answer-piping/). | Card | What it sends | More | | - | - | - | | Custom domain | Sends every email from your own domain | [Custom email domains](https://formstep.io/docs/branding-domains/custom-domains/#custom-email-domains) | | Notify on new submission | An email to you or your team for each response, with a PDF of the answers if you want | [Email notifications](https://formstep.io/docs/submissions-analytics/self-notifications/) | | Send confirmation to respondent | A confirmation to the person who answered, or to the recipient of a request | [Respondent confirmation](https://formstep.io/docs/submissions-analytics/self-notifications/#respondent-confirmation) | | Send reminders | Reminders for unfinished answers and open requests | [Reminders](https://formstep.io/docs/requests/invitations-and-reminders/#reminders) | | Request invitation | The email that sends a request link | [Invitations](https://formstep.io/docs/requests/invitations-and-reminders/#invitation) | On Free, new-submission notifications go to the workspace owner's address only. See [Limits & quotas](https://formstep.io/docs/subscription-billing/limits-quotas/) for what each plan includes. Emails to respondents use the form's language; translate your own text in [Translate](https://formstep.io/docs/building-forms/translating-form-content/). Emails to your team use the workspace's **Notification email language**. ## Integrations and Danger zone - **Integrations** opens the integrations dialog, also the plug icon next to the gear. See [Integrations](https://formstep.io/docs/integrations/overview/). - In **Danger zone**, **Delete all submissions** and **Delete analytics data** cannot be undone. **Delete form** moves the form to [Trash](https://formstep.io/docs/workspaces-teams/trash/) for 30 days. ## Next steps [Thank-you page & redirect](https://formstep.io/docs/building-forms/custom-thank-you/) — What respondents see after they submit [Close a form or cap responses](https://formstep.io/docs/sharing-publishing/scheduled-close/) — Close on a date, or cap responses per share link [Email notifications](https://formstep.io/docs/submissions-analytics/self-notifications/) — An email for each new response [Authentication gate](https://formstep.io/docs/sharing-publishing/authentication-gate/) — Sign-in or a password on the public link # Publish history View the published versions of your form and revert to an earlier one. Every plan can browse the history, and going back to a version needs Business. ## How it works Each publish saves a version: the form, its theme, logo, cover, emails, and translations. Business keeps the last 50 versions per form. Free and Pro keep the live version, plus older ones that a submission or an open link still uses. ## Open publish history In the editor, click **Publish history**, the clock icon in the top bar. Versions are listed newest first, and the live one is marked Live. Click a version to preview its **Form**, **Emails**, and **Translations**. ![The Publish History dialog: versions with their dates on the left, a preview of the selected version with Form, Emails, and Translations tabs, and Revert to this version](https://formstep.io/docs/images/building-forms/publish-history.png) Publish history: pick a version, preview it, and revert. To throw away edits you have not published yet, click **Revert unpublished changes** here. ## Go back to an earlier version Going back needs the workspace owner to be on Business. Pick a version and click **Revert to this version**, then choose: | Button | What it does | | - | - | | Open as draft | Loads the version into your editor as a draft. The live form stays unchanged until you publish again. | | Publish now | Restores the version and publishes it right away. Use this to undo a mistake fast. | A revert restores the form, cover, logo, theme, translations, and email text. Access settings, email recipients, integrations, and retention stay as they are. > **Reverting replaces your current draft** > > Reverting overwrites unpublished changes in the editor. Publish your draft first if you might want it back. ## Next steps [Publish checks](https://formstep.io/docs/building-forms/publish-checks/) — What blocks a publish, and what only warns [Plans & pricing](https://formstep.io/docs/subscription-billing/plans-pricing/) — What each plan includes # Publish checks What Formstep checks before your form goes live, which findings block publishing, and how to clear the ones you are most likely to meet. ## Where to find the checks Your edits save as a draft, and respondents see the last published version until you click **Publish**. The issue indicator beside Publish is green when all is well, amber for warnings, and red for errors. Click it to see the list, and click an entry to go where you fix it. The list updates as you type. ![Close-up of the editor toolbar on a published form with changes to publish: Edit, Translate, and Preview, then Publish history, Integrations, Settings, the green issue indicator, Publish changes, and the Inbox button.](https://formstep.io/docs/images/building-forms/preview-publish.png) The issue indicator sits beside Publish changes. ## Errors and warnings | Level | What happens when you press Publish | | - | - | | Error | Publishing stops, and nothing goes live. The same checks apply when an automation or AI agent publishes. | | Warning | A **Review needed** dialog lists the warnings. Fix them, or click **Publish** to go ahead. | For a settings warning, **Publish** also applies the fix the warning names, such as turning off a password gate that has no password. You fix content warnings yourself. ## Common errors | Message | How to fix | | - | - | | Variable “Email” references a deleted field and will show as empty. | An @ mention points at a question you deleted. Remove the mention or pick another question. | | A reference points to a field that no longer exists. | An email, a redirect parameter, an integration, or a translation still uses a deleted question. The entry names the place to update. | | A logic rule references a deleted field or page. | Update or remove the rule. The Logic tab lists it under Needs attention. | | Field key “total” is used by more than one field. | Give each field its own key in the Fields tab. See [Field keys](https://formstep.io/docs/requests/field-keys/). | | Complete setup for this Payment question before publishing. | Set a currency and amount, and connect Stripe. See [Payment collection](https://formstep.io/docs/building-forms/payment-collection/). | | Connect Cal.com before publishing forms that schedule appointments. | Connect Cal.com and pick an event type. See [Cal.com](https://formstep.io/docs/integrations/cal-com/). | ## Common warnings - “Page 2 has no content respondents can see.” The page holds only logic or hidden blocks, so respondents would walk into a blank step. Add content or remove the page break. - “A page jump sends respondents back from Page 3 to Page 2.” Respondents could loop between the two pages. Similar warnings flag a jump that targets its own page, or one on the last page, where Next submits the form. Review the rule. - “Field key “company\_name” will no longer exist after this publish.” Workflows that read it stop getting that answer. See [When a key is about to disappear](https://formstep.io/docs/requests/field-keys/#removed-keys). - “French: 12 strings missing.” Some text has no translation yet, or changed after it was translated. See [Multilingual forms](https://formstep.io/docs/building-forms/translating-form-content/). - “Slack connection expired.” The form publishes, but submissions stop reaching that app until you reconnect it. - “The decision question has no approve, decline or changes option.” Requests would finish with no outcome. Insert a Decision question from the / menu. ## Settings that Publish fixes for you When you publish anyway, Formstep turns these settings off or cleans them up: - A password gate with no password is turned off, so respondents can open the form. - [Edit after submit](https://formstep.io/docs/submissions-analytics/edit-after-submit/) is turned off on a form that does not allow it. - New-submission notifications with no valid address are turned off, and invalid addresses are dropped from the list. - A confirmation or reminder email with no email question to send to is turned off. - A redirect address that is not a valid web address is cleared, and respondents see your thank-you page. ## Other reasons a publish is refused These appear as a red notification when you press Publish: - The form is in the trash. Restore it first. - An email question has [respondent email verification](https://formstep.io/docs/building-forms/email-verification/) switched on and the workspace owner's plan is not Business. Upgrade, or switch verification off. - A field key you typed is already published on another field. Give each field its own key and publish again. ## Next steps [Publish history](https://formstep.io/docs/building-forms/version-history/) — Browse past versions and go back to one [Field keys](https://formstep.io/docs/requests/field-keys/) — Keep workflows matching after an edit --- ## Using AI chat # Using AI chat overview AI chat builds and edits forms, translates them and answers questions about your submissions. It runs on AI credits, which you buy in packs on any plan. ## What AI chat does Open it from **Ask anything** in the bottom-right corner and say what you need. It can build a form from a description, a PDF or a screenshot, change a form as you talk, translate it, and answer questions about your submissions. Before it publishes, deletes or cancels anything, it asks you to approve. ![AI chat floating over the bottom-right of the editor, with starter prompt suggestions, the message input, and the remaining AI credits above it.](https://formstep.io/docs/images/building-forms/ai-chat-panel.png) AI chat, floating over the editor. ## What it costs AI chat runs on credits that you buy in packs, on any plan. Credits never expire. If you would rather use your own AI tool, such as Claude or ChatGPT, [connect it to Formstep](https://formstep.io/docs/guides/ai-agents/connect/): that uses no Formstep credits. ## Pages in this section - [Use AI chat](https://formstep.io/docs/ai/using-ai/) — Build a form from a prompt, a PDF or a screenshot, edit and translate it, ask about your answers, and let AI chat do workspace tasks. - [AI credits & usage](https://formstep.io/docs/ai/ai-credits-usage/) — How AI credits work, what an AI action costs, and how to buy more. # Use AI chat Build a form from a prompt, a PDF or a screenshot, edit and translate it, ask about your answers, and let AI chat do workspace tasks. It asks before it publishes or deletes. ## Open AI chat Click **Ask anything** in the bottom-right corner, or press `⌘`+`A` / `Ctrl`+`A`. The chat floats over the page and stays open while you work, and its suggestions follow the page you are on. Say what you need in a sentence. If you are not sure yet, try _“Help me create a form”_ and AI asks questions until it has what it needs. AI chat needs a signed-in account and runs on [AI credits](https://formstep.io/docs/ai/ai-credits-usage/). > **Reply language and chat history** > > AI replies in your dashboard language; ask for another, such as _“reply in French”_. **New chat** clears the conversation for good, and **Minimize** hides it and keeps it. ## Build a form Describe the form: who it is for, what it should collect, and anything special such as a payment, a file upload or a question that appears only after a certain answer. AI builds the pages, questions, validation and logic, in a new form or in the one you have open. A detailed first message gets you a closer first draft, for example: > __ > > _Job application for a senior backend role. Ask for name, email, country, years of experience, a GitHub link and a CV upload (required). Ask if they need visa sponsorship, and if yes, ask a follow-up question._ ### From a PDF or a screenshot Attach a paper form, a PDF or a screenshot to your message, and AI builds matching questions. You can attach up to 5 files per message: JPEG, PNG or WebP up to 3 MB, GIF or PDF up to 1 MB. For a long or dense PDF, switch to Claude Sonnet 4.6 first. ## Change a form Keep chatting to change the result, or edit it by hand between messages. AI sees the form as it is now. For example: _“Add a phone number after email”_, _“Make question 3 required”_, or _“Split this into two pages”_. To have AI check answers while people fill in the form, add an [AI Validation](https://formstep.io/docs/building-forms/ai-validation/) block (Pro). ## Translate In **Translate**, AI fills in missing translations, updates outdated ones and polishes them, in one language or several. See [Translating form content](https://formstep.io/docs/building-forms/translating-form-content/). AI can also [translate a submission](https://formstep.io/docs/submissions-analytics/translating-submissions/) into your language. ## Ask about your answers On the Submissions tab, ask about your responses, or pick a suggestion such as **Give me the headline** or **Find trends and outliers**. ## Workspace tasks AI chat can also create forms and folders, publish forms, send and follow requests, and set up share links. ## AI asks first Before AI publishes, unpublishes or deletes something, changes a share link, or cancels a request, it shows the action with **Approve** and **Deny**, and does nothing until you approve. ## Choosing a model Pick a model with the model button under the message box; it applies to the current chat. Claude Haiku 4.5, the default, builds and edits forms well. Claude Sonnet 4.6 is better for questions about your answers, harder tasks and dense PDFs, and costs three times as much. ## Your own AI tool You can also work with Formstep from your own AI tool, such as Claude or ChatGPT. That uses no Formstep credits. See [Connect an AI agent](https://formstep.io/docs/guides/ai-agents/connect/). ## Next steps [AI credits & usage](https://formstep.io/docs/ai/ai-credits-usage/) — What an action costs and how to buy credits [AI Validation](https://formstep.io/docs/building-forms/ai-validation/) — Have AI check answers while people fill in the form [Multilingual forms](https://formstep.io/docs/building-forms/translating-form-content/) — Translate a form by hand or with AI [Connect an AI agent to Formstep](https://formstep.io/docs/guides/ai-agents/connect/) — Work with Formstep from Claude or ChatGPT # AI credits & usage How AI credits work, what an AI action costs, and how to buy more. Credits come in one-time packs on any plan, never expire, and belong to each person. ## How credits work Every AI action uses credits: a chat message, translating a submission, a summary of answers, or an AI Validation check. The more text and files AI reads, the more it uses. 100 credits are worth $1 of AI use, and a typical chat uses about 5. No plan includes credits, and every plan pays the same. ## Credit packs | Pack | Credits | About | | - | - | - | | Starter | 490 | 98 chats | | Pro | 1,990 | 398 chats | | Max | 4,990 | 998 chats | The Pro pack is a credit pack, separate from the Pro plan. Prices are in your currency, before tax. ## Buying credits Click **Add credits** in the sidebar, pick a pack and pay. Your balance goes up as soon as the payment completes. To protect against stolen cards, how much you can buy in 24 hours depends on your plan: one Max pack’s worth on Free, three on Pro, ten on Business. On Free and Pro, you wait an hour between packs. In its first 30 days, an account without a paid plan can buy up to $50 in all. For more, email [support](mailto:support@formstep.io). ## What an action costs A quick question costs about 1 credit, a typical chat 5, and a long task with many steps 25. Translating a submission costs about 1, and an AI summary of answers about 2. An AI Validation check costs under 1 credit for a text answer, and about 1 for a photo or a one-page PDF, plus 0.4 for each further page. Formstep runs Anthropic’s Claude models inside the EU. Claude Sonnet 4.6 costs three times as much as Claude Haiku 4.5, the default (see [choosing a model](https://formstep.io/docs/ai/using-ai/#models)). Costs follow what the model providers charge, so they can change. ## Your balance The sidebar and the AI chat show about how many chats you have left, and **Running low** at 100 credits or fewer. Without credits, AI stops and everything else keeps working. An action needs a few more credits free than it usually costs, because Formstep sets aside the most it could cost and then charges what it used. If your balance is too low, the chat says so. ## Who pays Credits belong to each person, not to the workspace, and each member buys their own. A form’s [AI Validations](https://formstep.io/docs/building-forms/ai-validation/) always use the workspace owner’s credits. On Business, the owner can turn on **Share my AI credits with members** in Workspace Settings. Members’ AI use in that workspace then spends the owner’s credits, and stops when they run out until the owner adds more. Sharing ends if the owner leaves Business. ## Your own AI tool When you use Formstep from your own AI tool, such as Claude or ChatGPT, it spends no Formstep credits. Your AI tool bills you. See [Connect an AI agent](https://formstep.io/docs/guides/ai-agents/connect/). ## Next steps [Use AI chat](https://formstep.io/docs/ai/using-ai/) — Build, edit, translate and analyze with AI [Plans & pricing](https://formstep.io/docs/subscription-billing/plans-pricing/) — What each plan is for --- ## Branding & Domains # Branding & Domains overview Give a form your own look, serve it from your own domain, and send its emails from your own address. What works on every plan and what needs Pro. ## The look of a form Each form has a theme: colors, fonts, a cover and a logo, in a light and a dark version. Presets work on every plan. Your own colors and fonts need Pro, and so does a workspace theme, which saves your look once for every form in the workspace. ![The Theme tab of the side panel with the Light and Dark switch, the Theme picker, and the typography and color controls](https://formstep.io/docs/images/building-forms/theme-panel.png) A form's theme, in the Theme tab of the editor. ## Your own domains On Pro and Business, forms can be served from your own domain and send their emails from your own address. The workspace owner sets both up once, and every member can then use them. ## Pages in this section - [Appearance & theming](https://formstep.io/docs/branding-domains/appearance-theming/) — Give a form your look with a preset or your own colors and fonts, a cover and a logo, and light and dark mode. - [Your own domains](https://formstep.io/docs/branding-domains/custom-domains/) — Serve forms from your own domain, such as forms.yourcompany.com, and send their emails from your own address. # Appearance & theming Give a form your look with a preset or your own colors and fonts, a cover and a logo, and light and dark mode. Save it as a workspace theme to reuse it on every form. ## Change the theme In the editor, click **Theme**, the palette icon on the right edge. On a phone, it is in the top bar’s **⋯** menu. The form shows each change as you make it. The **Light** and **Dark** switch picks which mode you are editing, and each mode has its own theme. Pick a theme in the **Theme** picker, adjust it, and click **Save**. ![The Theme tab of the side panel with the Light and Dark switch, the Theme picker, and the typography and color controls](https://formstep.io/docs/images/building-forms/theme-panel.png) The Theme tab of the side panel. ## Presets A preset sets the colors, the font and the corner rounding in one click, and every plan can use them. New forms start on **Formstep default**. Once you change a value by hand, the picker shows **Custom**. > **Your own theme needs Pro** > > Changing colors and fonts yourself needs Pro or Business. After a downgrade, a form keeps its own theme, but you cannot edit it. ## Colors and fonts Under **Typography**, pick one of eleven fonts, the base text size and the corner rounding of fields and buttons. The colors are grouped by where they show on the form: the page, fields and choices, buttons and errors, and rating stars. Each color changes everything with the same purpose, so **Text** changes all text on the form. Point at a color’s name to see what it controls. ## Cover image and logo Click **Add Cover** or **Add Logo** above the form. Upload an image (PNG, JPG or WebP up to 5 MB), or pick a color, an Unsplash photo or an icon. Drag a cover up or down to place it. ## Dark mode A form follows the light or dark setting of the respondent’s device. Without a dark theme of your own, dark mode uses Formstep’s default dark colors, so set up both modes, and check that your logo and button colors work on both. An [embed](https://formstep.io/docs/sharing-publishing/embedding-popups/#color-scheme) can force one mode. ## Workspace themes A workspace theme saves a look under a name, so every form in the workspace can use it. Change the theme, and every form linked to it changes. Any member can save one on Pro or Business. A theme covers one mode, so keep two for both, such as _Acme Light_ and _Acme Dark_. 1. Style a form in the **Theme** tab, click **Save as workspace theme**, name it, and click **Save theme**. This form is now linked to it. 2. On another form, open **Theme**, pick the theme under **Workspace themes**, and click **Save**. The form shows a **Linked** badge. When you change a linked form’s theme, Formstep asks whether to save it to the workspace theme, which updates every linked form, or for this form only. Respondents see the new look after you publish each form again. Rename and delete a theme from the **⋯** menu beside it; after a delete, forms keep the look as their own theme. ## Formstep branding Forms show a small Formstep badge. On Pro and Business, turn off **Show Formstep branding** in **Settings**, **General**. ## Next steps [Your own domains](https://formstep.io/docs/branding-domains/custom-domains/) — Serve forms and send emails from your domain [Multilingual forms](https://formstep.io/docs/building-forms/translating-form-content/) — Serve your form in any language # Your own domains Serve forms from your own domain, such as forms.yourcompany.com, and send their emails from your own address. Set both up on the Domains page. This needs the Pro plan or higher. [Compare plans](https://formstep.io/pricing/) ## The Domains page Click **Domains** in the sidebar. The workspace owner sets domains up there, and every member can then use them. A domain that serves forms and a domain that sends email are set up separately, with different DNS records, even when the name is the same. Both kinds count together toward your plan’s [domain limit](https://formstep.io/docs/subscription-billing/limits-quotas/#plan-limits). ## Serve forms from your domain 1. **Add the domain** On the Domains page, click **Add domain** and enter a subdomain, such as forms.yourcompany.com. A root domain like yourcompany.com does not work. 2. **Add a CNAME record** At your DNS provider, create the record below. No other records are needed. 3. **Click Check DNS** Formstep finds the record and sets up a security certificate. The status changes only when you click **Check DNS**, so if it is still pending, wait a few minutes and click again. 4. **Add your bot protection keys** Bot protection on your own domain needs your own Cloudflare Turnstile keys. In the **Bot Protection** card, click **Get keys**, paste the **Site key** and **Secret key**, and click **Save keys**. See [bot protection](https://formstep.io/docs/sharing-publishing/captcha-bot-protection/#custom-domains). 5. **Use the domain** When the domain shows **Active**, open a form's **Public link** sheet, pick the domain under **Link**, and type a short address. | Field | Value | | - | - | | Type | CNAME | | Name | Your subdomain, such as forms | | Target | form.formstep.io | If the domain shows **Error**, the record is usually missing or wrong. Fix it and click **Check DNS** again. ### Readable addresses A share link on your domain gets a readable address, such as `forms.yourcompany.com/feedback` instead of `form.formstep.io/a1b2c3d4`. Use lowercase letters, numbers and hyphens. The link’s formstep.io address keeps working. ### Link previews Click the sliders icon on the domain card to set the title, description, image and icon shown when a link is shared, and to hide the domain’s forms from search engines. A form’s own [share link preview](https://formstep.io/docs/sharing-publishing/sharing-embedding/#social-preview) takes priority. ### Removing a domain Deleting a domain moves its share links back to form.formstep.io, and links on your domain stop working. Click the link icon on an active domain first to see which share links use it. ## Send emails from your domain With an email domain, request invitations, reminders, confirmations, notifications and verification codes come from your own address. Recipients see your domain as the sender, and mail from a domain you verified is less likely to land in spam. 1. **Add the email domain** On the Domains page, click **Add email domain** and enter the domain you send from, such as yourcompany.com. 2. **Add three CNAME records** Formstep shows three records. Copy each name and value to your DNS provider exactly as shown. 3. **Click Check DNS** When all three records are found, the domain shows **Verified**. If not, wait a few minutes and click again. 4. **Pick it in each form** In the form, open **Settings**, **Emails**, and pick the domain under **Custom domain**. Emails then come from noreply\@yourcompany.com. > **If verification fails** > > Some DNS providers add your domain to the record name on their own. Check that each record looks exactly as Formstep shows it. If you remove an email domain, its forms send from again. ## If your plan ends Your domains keep working for a while, and the emails you get name the date they stop. See [what ends on its own schedule](https://formstep.io/docs/subscription-billing/upgrading-downgrading/#what-gets-deleted). ## Next steps [Appearance & theming](https://formstep.io/docs/branding-domains/appearance-theming/) — Colors, fonts, cover and logo [Respondent notifications](https://formstep.io/docs/submissions-analytics/self-notifications/#respondent-confirmation) — Confirmations sent from your domain --- ## Requests # Requests overview A request sends a form to one named person, with what you already know filled in. See how it differs from the public link and what happens after you send it. ## What a request is A form is the design you build once. A request sends it to one person, with their own link and their own answers filled in. You see whether they have opened it and answered, Formstep can remind them, and the answers come back to you, or to the workflow that sent it. ![A request open in the recipient's browser: their name filled in and locked with the note Filled in by the sender, their email filled in, and the rest of the form left for them to answer.](https://formstep.io/docs/images/requests/recipient-prefilled.png) What the recipient sees: their answers filled in, some locked. | | Public link | Request | | - | - | - | | Who answers | Anyone with the link | One person you name | | How many answers | Any number | One | | Filled in for them | Nothing, or the same for everyone | Their own answers, some locked | | You can follow up | No | Yes: see who answered and remind them | Any published form can be sent as a request, and the same form can have a [public link](https://formstep.io/docs/sharing-publishing/overview/) too. ## What happens after you send it 1. [They get their link](https://formstep.io/docs/requests/invitations-and-reminders/) — In an invitation email from Formstep or from you. They can open it as often as they like, on any device. 2. **They see your form** In your brand and their language, with their answers filled in. 3. [They submit](https://formstep.io/docs/requests/managing-requests/) — The answers land in the form's inbox and on the Requests page, and the workflow that sent the request is told. If they do not answer, reminders follow up, and the request expires after 30 days unless you choose otherwise. You can see where each request stands on the [Requests page](https://formstep.io/docs/requests/managing-requests/). ## Pages in this section - [Send a request](https://formstep.io/docs/requests/creating-requests/) — Send a published form to one person from the New request sheet, with the answers you already know filled in and locked where they may only confirm. - [Bulk send](https://formstep.io/docs/requests/bulk-send/) — Send one form to a whole list at once on Pro and Business: type, paste, upload a CSV or pick HubSpot contacts, and each person gets their own request. - [Field keys](https://formstep.io/docs/requests/field-keys/) — The short names Zapier, Make, n8n, AI agents and your own code use for the fields of a form: where to find them, and how to keep them stable. - [Decisions & approvals](https://formstep.io/docs/requests/decisions-and-approvals/) — Ask for a yes, a no or a request for changes, and let your workflow act on the answer. - [Invitations, reminders & expiry](https://formstep.io/docs/requests/invitations-and-reminders/) — Let Formstep email the request link, chase people who go quiet, and close requests that go stale. - [The Requests page](https://formstep.io/docs/requests/managing-requests/) — See everything you are waiting on, follow one request through its timeline, and act on it. - [Troubleshooting requests](https://formstep.io/docs/requests/troubleshooting/) — An invitation that never arrived, a link that shows a notice instead of the form, a workflow that was not told, or one that stopped matching after an edit. # Send a request Send a published form to one person from the New request sheet, with the answers you already know filled in and locked where they may only confirm. ## Send one by hand 1. **Open the New request sheet** Open a published form and click **New request**, the paper plane icon at the top right of the form page. 2. **Say who it is for** Under _Who is it for?_, enter their email and name. The email is needed even if you will send the link yourself. 3. **Fill in what you know** Under _What they see_, fill in the answers you already have. Click **Lock** on any answer they may only confirm. 4. **Choose Email or Link** **Email** has Formstep send the invitation, on Pro and Business. On Free, your first [10 invitations](https://formstep.io/docs/subscription-billing/limits-quotas/#free-invitations) go to your own address only. **Link** emails nothing and gives you the link to send however you like. 5. **Send it** Click **Send invitation** or **Create link**. The request appears on the [Requests page](https://formstep.io/docs/requests/managing-requests/). ![The New request sheet with a recipient, two prefilled answers, one of them locked, and Email or Link beside Send invitation](https://formstep.io/docs/images/requests/request-sheet.png) The New request sheet: who it is for, what they see, and how it reaches them. Under **Callback and options** you can change when the request expires (30 days unless you set 1 to 365) and add an **External id**, your own reference such as a CRM record. To send the same form to a list of people, see [Bulk send](https://formstep.io/docs/requests/bulk-send/). ## Prefill, locked fields and hidden fields A request can carry three kinds of values for one person. | Value | Use it for | The recipient | | - | - | - | | Prefill | Answers you already know, such as their company name | Sees it and can change it | | Locked field | Facts they only confirm, such as a contract number | Sees it and cannot change it | | Hidden field | Values the form needs but does not ask, such as a case number | Cannot change it, and sees it only where you show it | ### Prefill Prefilled answers save the recipient typing: they check and correct instead. Formstep does not look the values up for you. Fill them in by hand, or let your workflow look them up first, for example in [HubSpot](https://formstep.io/docs/integrations/hubspot/#requests). ### Locked fields A locked answer is shown but cannot be changed. Locking applies to this one request, not to the form. A locked answer needs a value, or the recipient could never submit. ### Hidden fields Values for the form’s [hidden fields](https://formstep.io/docs/building-forms/hidden-fields/), such as a case number or an amount. Logic, calculated fields and email text can use them, and they come back unchanged with the answers. They are not secret: the recipient sees a value wherever you [mention](https://formstep.io/docs/building-forms/answer-piping/) it in the form or the invitation. Repeating groups, multi-select questions, matrices and files can only be filled in from a workflow. The sheet says how many such fields the form has. ## Try it on yourself first **Try it yourself**, at the bottom of the sheet, creates a test request from what you filled in and gives you its link. Answer it as the recipient would. A test request emails nobody, does not count toward your monthly allowance, and expires after a day. On Free you can make 10 a day. Its answers stay out of the inbox, stats, exports and integrations. To find it on the Requests page, open **Filter** and tick **Show test requests**. ## What the recipient sees Your form, in your theme and their language, with their answers filled in and locked answers read-only. There is no captcha and no sign-in. A request has no message of its own, so put what they need to know in the form. A [mention](https://formstep.io/docs/building-forms/answer-piping/) of a prefilled or hidden value makes it personal. ![A request opened by the recipient: their name filled in and locked, with the note 'Filled in by the sender.', and their email filled in](https://formstep.io/docs/images/requests/recipient-prefilled.png) The recipient opens your form with the prefilled answers in place. ## Send requests from a workflow Zapier, Make, n8n, an AI agent or your own code can create the same request, with the same prefill, locks and hidden fields. Fill in the sheet once, then open the **⋯** menu and choose **From an automation**: it shows the request you filled in as a ready-made call. See the [automation guides](https://formstep.io/docs/guides/overview/), or [Create requests from code](https://formstep.io/docs/developers/requests-api/) for every option. > **What a request costs** > > Each request counts once toward your [monthly allowance](https://formstep.io/docs/subscription-billing/limits-quotas/) when you create it, whether or not anyone answers. Public link submissions share the same allowance. ## Next steps [Bulk send](https://formstep.io/docs/requests/bulk-send/) — Send one form to a list of people [Invitations, reminders & expiry](https://formstep.io/docs/requests/invitations-and-reminders/) — The email, the reminders and when a request ends [The Requests page](https://formstep.io/docs/requests/managing-requests/) — Follow every request you sent [Field keys](https://formstep.io/docs/requests/field-keys/) — The names a workflow uses for your fields # Bulk send Send one form to a whole list at once on Pro and Business: type, paste, upload a CSV or pick HubSpot contacts, and each person gets their own request. This needs the Pro plan or higher. [Compare plans](https://formstep.io/pricing/) ## Start a list Open your published form and click **New request**. Under **Who is it for?**, click **Add another person**, or **Import** to upload a CSV, paste a list, or find HubSpot contacts. With two or more people, the sheet shows a table. The rest of the sheet works as it does for one person. ![The New request sheet with five people in a table with Company name and case\_id columns, Everyone and Need a fix tabs with Import and Columns above it, one address outlined red, Add person under it, and Email or Link beside Create 4 links in the footer](https://formstep.io/docs/images/requests/bulk-send-table.png) A send to several people: everyone in one table, the row that needs a fix outlined red and counted in Need a fix, and Send counting only the rows that can go. ## Add people You can mix these sources in one list: | Source | How | | - | - | | Type | Click Add person under the table, then type the name and the email address into the new row. | | Paste | Paste one person per line into any Name or Email cell, or into Paste a list in Import. Columns copied from a spreadsheet work too. | | Upload CSV | Pick a CSV whose first row names the columns: email (required), name, and any of the form's field keys. Download CSV template, in Import, downloads exactly those headers. | | HubSpot | In Import, choose Find in HubSpot, search the workspace's HubSpot account by name or email, tick the ones you want and click Add. Contacts already in the table show as Added. | A CSV column named after a field key fills that field for each person. A choice column takes the option’s key or label, a switch column `true` or `false`. Columns that match no field are ignored and listed under the table. Commas, semicolons, and tabs all work as separators. A HubSpot contact adds only an email address and a name. See [HubSpot and requests](https://formstep.io/docs/integrations/hubspot/#requests). ## Columns and what everyone shares Each field is either a **column**, with a value per person, or the same for everyone. A CSV makes its fields columns. To choose yourself, click **Columns** above the table. Fields that are not columns appear under **What they see**, with one value for everyone. A lock applies to every person, so a locked column needs a value in every row. ## Rows that need a fix The table checks every row and outlines a bad cell in red. Hover it to see why. Rows with a red cell are not sent; **Need a fix**, above the table, counts and lists them. Edit the cell to fix it. | Red cell | Why | | - | - | | Email | No address, an address that is not one, or a second row for an address already in the list. | | A number column | A value that is not a number. | | A choice column | A value that is neither an option's key nor its label. | | A locked column | An empty cell: the person could never fill it in. | ## Send 1. **Pick the delivery** **Email** sends each person their link and reminders. **Link** emails nobody and gives you the links. 2. **Click Send** The button shows the count, such as **Send 20 invitations**. From 20 invitations on, Formstep asks you to confirm first. 3. **Read the receipt** The receipt shows how many went out. **Download links** saves a CSV with each person's link. **Open in Requests** shows the send. People who were sent leave the table. Anyone not sent stays, with the reason, so you can fix it and send again. To check what people will get first, click **Try it yourself**: it sends a test request to you, built from the first row. ## In Requests Each person’s request is an ordinary request: you can remind, cancel, or delete one without touching the others. On the [Requests page](https://formstep.io/docs/requests/managing-requests/), a send shows under one heading, such as _Sent to 4 people_. Click it, or pick the send under **Filter**, **Sent together**, to see only that send. ## Limits | Limit | What happens | | - | - | | 500 people per send | The table stops adding people at 500 and says so. | | Monthly allowance | Each request spends one unit, like any request. When the allowance runs out partway, the send stops there and the rest stay in the table. | | Invitations per hour | Formstep emails at most 200 invitations an hour per account, 30 in its first 7 days. A larger send emails as far as it can; the rest stay in the table to send later, or as links. | | Plan | Pro and Business. On Free, the sheet sends to one person at a time. | A workflow sends to a list by creating one request per person. See the [automation guides](https://formstep.io/docs/guides/overview/). ## Next steps [Send a request](https://formstep.io/docs/requests/creating-requests/) — Prefill, locked fields and hidden fields, one person at a time [The Requests page](https://formstep.io/docs/requests/managing-requests/) — Follow every request, filter by send, remind and cancel # Field keys The short names Zapier, Make, n8n, AI agents and your own code use for the fields of a form: where to find them, and how to keep them stable. ## What they are for When Zapier, Make, n8n, an AI agent or your own code fills in a request or reads the answers, it names each field by its key. A question titled “Company name” gets the key `company_name`. You only need to care about keys if a workflow uses your form. ## Where to find them ![The Fields tab of the editor's side panel with three sections: Questions listing Company name, Company website and Work email with their type icons and keys, Hidden Fields with vendor\_id, and AI Validation with the two checks named by their sentences and their keys website\_check and email\_domain.](https://formstep.io/docs/images/requests/keys-table.png) The Fields tab lists every key, and you can change each one in place. In the editor, click **Fields** (the key icon) on the right edge. On a phone, it is in the top bar’s **⋯** menu. The tab lists every key: questions with their options, hidden fields, calculated fields and [AI Validations](https://formstep.io/docs/building-forms/ai-validation/). Click a field’s name to jump to it in the form. You do not have to set anything: a key comes from the question’s title until you change it. To choose your own, type it in the Fields tab. Use letters, digits and underscores. Each key must be unique in the form. A title with no Latin letters, such as one in Arabic or Chinese, gets a key like `field_3`, so give it a readable one if a workflow reads it. ## Keys stay the same after publishing From the first publish on, each field keeps its key: - Rewording a question keeps the key. “Company name” renamed to “Legal entity name” is still `company_name`. - Moving a question, or changing its type, keeps the key. - Duplicating a question gives the copy a new key. > **Changing a published key breaks workflows** > > If you type a new key on a published form, every workflow that uses the old one stops matching once you publish. Update the workflow at the same time. ## When a key is about to disappear Deleting a question and adding a new one in its place gives the new question a new key, so workflows lose that answer. Publishing warns you first and names the field. To keep your workflows working, open the **Fields** tab and type the old key on the new field. If you removed the field on purpose, publish anyway and update the workflows. ## Option keys Every choice of a choice question, and every row and column of a matrix, has a key of its own too, such as `pro` for the choice “Pro plan”. A workflow can check which choice was picked in any language the recipient used. Option keys come from the label, you can change them in the Fields tab, and they stay the same after publishing. Developers: [Create requests from code](https://formstep.io/docs/developers/requests-api/) shows how keys appear in what a workflow sends and receives. ## Next steps [Send a request](https://formstep.io/docs/requests/creating-requests/) — Prefill and lock fields for one person [Troubleshooting requests](https://formstep.io/docs/requests/troubleshooting/) — When a workflow stops matching after an edit # Decisions & approvals Ask for a yes, a no or a request for changes, and let your workflow act on the answer. ## Why a special question Every author words “yes” differently, which makes a plain choice question hard for a workflow to act on. The **Decision** question always records one of three fixed values, `approve`, `decline` or `changes`, whatever its labels say and in any language. When the recipient picks one, the request ends with that **outcome**. ## Add one to your form 1. **Add the question** In the editor, click **Decision** in the Blocks tab, or press **/** and pick it. 2. **Reword the labels** The question reads "Do you approve?" with Approve, Decline, and Request changes. Reword them as you like, for example "Sign off" or "Send back for edits". 3. **Ask why, if you need to** Add a long-text question after it, and use [conditional logic](https://formstep.io/docs/building-forms/conditional-logic/) to show it only for decline and changes. 4. **Publish** Requests sent after you publish end with an outcome. > **Keep the three keys** > > Reword the labels freely, but keep all three options and their [keys](https://formstep.io/docs/requests/field-keys/#option-keys). Publishing warns you if one is missing. A Decision question inside a [repeating group](https://formstep.io/docs/building-forms/repeating-groups/) gives no outcome. ## The three values | Value | Default label | Means | | - | - | - | | approve | Approve | The recipient agreed. | | decline | Decline | They said no. The request still completes, with decline as its outcome. | | changes | Request changes | They want something changed before they agree. Usually the cue to send a new request. | If you add a fourth option, picking it is an ordinary answer: the request completes with no outcome. ## Where the outcome shows up - On the [Requests page](https://formstep.io/docs/requests/managing-requests/): Approved, Declined or Changes requested on the request, and an **Outcome** filter. - In the form’s submissions, as an ordinary answer to the Decision question. - In what a waiting workflow receives, so Zapier, Make or n8n can take a different path for each verdict. Expired and canceled requests have no outcome. An AI agent can add a Decision question and read the outcome too. Developers: see [Request callbacks](https://formstep.io/docs/developers/callbacks/#payload). ## Next steps [Option keys](https://formstep.io/docs/requests/field-keys/#option-keys) — The names a workflow reads for each choice [The Requests page](https://formstep.io/docs/requests/managing-requests/) — Filter requests by outcome # Invitations, reminders & expiry Let Formstep email the request link, chase people who go quiet, and close requests that go stale. Emails need Pro or Business, and expiry works on every plan. This needs the Pro plan or higher. [Compare plans](https://formstep.io/pricing/) On every plan you can send the request link yourself, by email, chat or text message, and every request expires. Emails from Formstep need Pro or Business; see [what Free includes](https://formstep.io/docs/subscription-billing/limits-quotas/#free-invitations). ## The request invitation Choose **Email** in the New request sheet, and Formstep sends the **request invitation**: a short email with a button that opens the request. A workflow can ask for the same email. You write it once per form. Open **Settings**, the **Emails** tab, and scroll to **Requests** at the bottom: | Field | What it is | | - | - | | From | noreply\@formstep.io, or your own address once you set up a custom email domain and pick it for the form. | | Subject | Optional. Left empty, Formstep sends "We need a few things from you". Mentions are allowed. | | Body | Optional. Left empty, Formstep sends its built-in message; Preview shows exactly what goes out. Mentions are allowed. | | Button | Labelled Open request; it carries the request link. Always present, not editable. | To personalise the email, mention a prefilled answer or a hidden field in the subject or body. If the form has more languages, the email uses the request’s language; [translate the email text](https://formstep.io/docs/building-forms/translating-form-content/) with the rest of the form. - The invitation is sent once. If the request ends before it goes out, it is not sent. - If it never arrived, see [Troubleshooting](https://formstep.io/docs/requests/troubleshooting/#invitation-problems). - One recipient address gets at most 10 emails a day per form, invitations and reminders together. ## Reminders A reminder schedule emails someone who has gone quiet, for example 1 day, 3 days and 1 week after their last saved answer. It reminds request recipients who have not submitted, and people who left an unfinished draft on a share link. Turn it on in **Settings**, **Emails**, **Send reminders**. | Setting | What it does | | - | - | | Send reminders | Turns reminders on for requests and public-link drafts. | | Reminder schedule | Up to five steps, chosen from 12 hours, 1 day, 3 days, 1 week, and 2 weeks. | | Public-link drafts | To: the question that holds the respondent's email address. Wait for: remind only once these questions are answered. Requests ignore these and always remind the recipient. | | Subject and body | One reminder email for both. Mentions are allowed. | ### How the schedule runs - Each step counts from the last answer the recipient saved, or from when the request was created if they have saved nothing. Every saved answer starts the schedule over. Opening the link does not, so a mail scanner that opens it cannot reset it. - Reminders stop when the request is completed, expired, or canceled. - Reminders go out within 15 minutes of the step’s time. - One request gets at most 8 reminders, automatic and manual together. ### A different schedule for one request A workflow can give one request its own reminder schedule, or none, even when **Send reminders** is off. Otherwise the request uses the form’s schedule. ### Chasing someone now Click **Send reminder** on the request page to send one now. The schedule carries on as before. Leave at least 10 minutes between manual reminders. ## Expiry When a request expires, the link shows a short notice instead of the form, the status becomes **Expired**, and a waiting workflow is told. - Default: **30 days** after creation. Maximum: **365 days**. - Set it in the New request sheet under **Callback and options**, **Expires in (days)**. - The link stops working right away; the status and callback follow within 15 minutes. For an overview of what reminders do and what they cost, see [Form reminders](https://formstep.io/form-reminders/) on formstep.io. ## Next steps [Send emails from your domain](https://formstep.io/docs/branding-domains/custom-domains/#custom-email-domains) — Invitations and reminders from your own address [Troubleshooting requests](https://formstep.io/docs/requests/troubleshooting/) — When an invitation never arrived # The Requests page See everything you are waiting on, follow one request through its timeline, and act on it. Send a reminder, cancel it, replay its callback, or delete it. ## The list Click **Requests** in the sidebar. Each row shows the recipient, the form, and where the request stands, such as “Not opened · sent 2 hr. ago”. Click a row to open the request beside the list, and press `↑` or `↓` to move between rows. On a narrow screen the request opens on its own page. ![The Requests page: the list on the left, a send of four people under its heading and two single requests, and Sara Lindqvist's request open beside it](https://formstep.io/docs/images/requests/requests-page.png) The Requests page: every request in the workspace, with the one you picked beside the list. | You see | It means | | - | - | | Not opened | The link has never been opened. The detail line shows reminders sent, or how soon it expires. | | Opened | They opened the link but have not answered anything yet. | | In progress | They have started answering. The detail line shows how many questions are answered. | | Completed / Expired / Canceled | The request has ended, on the date shown. A completed request cannot be answered again. An expired or canceled one is deleted 30 days after it ended. | | Callback failed | A red badge: the request ended, but the workflow waiting for it could not be told. See below. | | Test | A grey badge: a test request, so nothing was emailed and its answers count nowhere. | - **Search** finds a recipient’s name or email, or your external id. - **Sort** by Created, Last activity, or Name. - **Filter** by status, [outcome](https://formstep.io/docs/requests/decisions-and-approvals/), form, send, or **Callback failed**. Tick **Show test requests** to see test requests. The requests of one [bulk send](https://formstep.io/docs/requests/bulk-send/) are grouped under one heading. Click it to see only that send. ## One form’s requests Open a published form and pick the **Requests** tab at the top. The list works like the Requests page, for this form only. ![A form page on its Requests tab: the Overview strip open with Pending, Completed, Time to complete, Time to open, Invitations sent, and Expired or canceled, and a few requests listed below](https://formstep.io/docs/images/requests/requests-overview.png) The form page's Requests tab: the Overview strip for the selected period, then this form's requests. ### The Overview strip The **Overview** strip shows six figures for the requests created in the chosen period, the last 30 days by default. Click its header to fold it. It works on every plan. | Figure | What it means | | - | - | | Pending | Requests sent and not answered yet. Test requests are left out. | | Completed | The share of requests created in the range that were completed, with "X of Y" beneath it. | | Time to complete | Median time between creating a request and the recipient completing it. | | Time to open | Median time between creating a request and the recipient first opening its link. | | Invitations sent | Invitation emails sent, out of the requests set to be emailed. It does not confirm they reached the inbox. | | Expired or canceled | Requests that expired, or were canceled, before the recipient completed them. | ## One request in detail Each request has its own address, so you can bookmark it or share it with a colleague. The heading shows the recipient, where the request stands, their email, the language, who sent it and when, and when it expires. ![A completed request: the answers listed with their field keys on the left, the form and timeline on the right](https://formstep.io/docs/images/requests/request-page-detail.png) A completed request's page. ### Answers Every field of the form, with its field key and answer. **Prefilled** marks values you sent, and a lock marks the ones the recipient could not change. While the request is pending, you see what you prefilled and _Not answered yet_ for the rest. On Pro and Business, you also see the answers the recipient has saved so far. [AI Validation](https://formstep.io/docs/building-forms/ai-validation/) results show after the answers they checked: **Passed**, **Needs review** with a reason, or **Not checked**. **Translate** shows a completed request’s answers in your workspace language. ### Form, timeline and callback Beside the answers, **Preview** shows the form as the recipient sees it, **Go to form** opens the form’s page, and **Download** saves a completed request as a PDF. The timeline lists everything that happened, newest first, with failures in red. Email and callback entries leave the timeline after 30 days, while created, opened and completed stay. If a workflow is waiting on the request, the Callback section shows [whether it was told](#callback). ### What you can do - **Send reminder** emails a pending request’s recipient now, on Pro and Business. The reminder schedule carries on as before. - **Cancel request**, in the **⋯** menu, withdraws a pending request. The link stops working, and a waiting workflow is told. - **Copy link** copies the recipient’s link, so you can send it another way. - **Delete request**, in the **⋯** menu, erases the request. See below. ### When a workflow is waiting A request sent by Zapier, Make, n8n, an AI agent or your own code can ask to be told when it ends. When the recipient submits, or the request expires or is canceled, Formstep sends the outcome and the answers to that workflow. This message is the _callback_, and the request’s **Callback** section shows whether it arrived. If the workflow could not be reached, Formstep keeps trying for a few hours. If it still fails, the request gets a **Callback failed** badge and the workspace owner gets an email. Once the workflow is fixed, click **Replay** to send it again. Developers: see [Request callbacks](https://formstep.io/docs/developers/callbacks/). ### Deleting a request Deleting cannot be undone. It removes the request with its answers, files, documents, and history. The link stops working, and no callback is sent. Use it when a recipient asks you to erase their data. To stop a request and tell your workflow, cancel it instead. Formstep also deletes requests on its own: an expired or canceled request without answers [30 days after it ended](https://formstep.io/docs/submissions-analytics/submission-retention/#unanswered-requests), and ended requests once the form’s [retention window](https://formstep.io/docs/submissions-analytics/submission-retention/#retention-and-requests) passes. ## Next steps [Troubleshooting requests](https://formstep.io/docs/requests/troubleshooting/) — What to do when something failed [Invitations, reminders & expiry](https://formstep.io/docs/requests/invitations-and-reminders/) — Set the schedule these actions work with # Troubleshooting requests An invitation that never arrived, a link that shows a notice instead of the form, a workflow that was not told, or one that stopped matching after an edit. ## The invitation never arrived Open the request on the [Requests page](https://formstep.io/docs/requests/managing-requests/) and read its timeline. The first invitation line tells you which case you are in. | Timeline says | What it means | What to do | | - | - | - | | Invitation queued | Accepted and about to be sent. | Give it a minute. If it stays queued, check the request has an email address. | | Invitation sent | The email left Formstep. | Ask the recipient to check their spam folder. Sending from your own email domain helps. | | Invitation failed | Formstep could not send it, the address bounced, or the recipient marked it as spam. | If the address is wrong, send a new request to the right one. Otherwise copy the link and send it yourself. | | No invitation line | The request was created with Link, so nothing was emailed. | Copy the link and send it yourself. | Sending from [your own email domain](https://formstep.io/docs/branding-domains/custom-domains/#custom-email-domains) makes invitations more likely to reach the inbox. On Free, Formstep emails only your first 10 invitations, to your own address. ## The link shows a notice instead of the form The request has ended: it was completed, it expired, or it was canceled. A completed request cannot be answered again. If the recipient needs another go, send a new request. ## The workflow was not told The request page’s **Callback** section shows where Formstep tried to send the outcome and what happened. _Callback failed_ means Formstep tried for about four hours and gave up. Ask whoever runs the workflow to fix it, then click **Replay** to send it again. Developers: see [Request callbacks](https://formstep.io/docs/developers/callbacks/). ## A workflow stopped matching after an edit An answer is missing from what the workflow receives, or it can no longer fill in a field. A published field key was changed, or its question was deleted and replaced. Type the old key on the field in the editor’s **Fields** tab and publish again. See [When a key is about to disappear](https://formstep.io/docs/requests/field-keys/#removed-keys). Developers: see the [API error reference](https://formstep.io/docs/developers/rest-api/#error-reference) for every error a request call can return. ## Next steps [The Requests page](https://formstep.io/docs/requests/managing-requests/) — Read a request's timeline and callback [Invitations, reminders & expiry](https://formstep.io/docs/requests/invitations-and-reminders/) — The email, the reminders and when a request ends --- ## Public Link # Public Link overview Share a published form with anyone through one share link or several, as a link, an embed, a popup or a QR code. When to use the public link and when a request. ## How the public link works Click **Public link**, the link icon at the top right of the form page. Publishing gives the form its first share link. Anyone who has it can open the form and submit, so one share link collects any number of answers. Add more share links when you want to tell sources apart: each has its own name, language, expiry, response limit and analytics. ![The Public link sheet with the link, the QR Code, Embed, and Popup cards, and the Link, Appearance, Limits, and Query parameters sections](https://formstep.io/docs/images/building-forms/share-panel.png) The Public link sheet, from the link icon at the top of the form page. ## Public link or request A [request](https://formstep.io/docs/requests/overview/) is the other way to send the same form. It goes to one named person, and you can see whether they answered. One form can use both at once. | If you… | Use | | - | - | | Collect from people you cannot name, as with a signup, a survey or a contact form | Public link | | Want the form on a web page, in an email footer or behind a QR code | Public link | | Need one specific person to supply, confirm, sign or pay | Request | | Want to see whether a named person has answered, and remind them | Request | | Already know some answers and want them filled in for the person | Request | ## Pages in this section Share and embed - [Share links](https://formstep.io/docs/sharing-publishing/sharing-embedding/) — Create and manage share links for your form. - [Embedding & popups](https://formstep.io/docs/sharing-publishing/embedding-popups/) — Embed your form on a website or show it as a popup over the page. - [Embed a form in Webflow](https://formstep.io/docs/sharing-publishing/embed-in-webflow/) — Put a Formstep form inside a Webflow page with a Code Embed element, or open it as a popup from a button. - [Embed a form in WordPress](https://formstep.io/docs/sharing-publishing/embed-in-wordpress/) — Put a Formstep form inside a WordPress page or post with a Custom HTML block. - [Embed a form in Framer](https://formstep.io/docs/sharing-publishing/embed-in-framer/) — Put a Formstep form inside a Framer page with the Embed component, from the share link or from the embed code. - [Embed a form in Notion](https://formstep.io/docs/sharing-publishing/embed-in-notion/) — Put a Formstep form inside a Notion page with an Embed block. Who can answer, and until when - [Authentication gate](https://formstep.io/docs/sharing-publishing/authentication-gate/) — Require respondents to sign in or enter a password before they open your share link, and pick the gate that fits the form. - [Bot protection (Turnstile)](https://formstep.io/docs/sharing-publishing/captcha-bot-protection/) — Defend share links against bot submissions with Cloudflare Turnstile. - [Close a form or cap responses](https://formstep.io/docs/sharing-publishing/scheduled-close/) — Stop a form taking answers now or on a date, cap how many responses one share link accepts, and limit how often one person can submit. # Share links Create and manage share links for your form. Give each one its own name, expiry, limit and analytics, tag it with UTM parameters, or print it as a QR code. ## Create and manage links One form can have several share links, each with its own settings. ### Creating a share link 1. **Publish your form** The first publish creates the form's first share link. 2. **Open the Public link sheet** Open the form and click **Public link**, the link icon at the top right. Pick a link from the dropdown, or click **Create new link**. 3. **Name the link** Under **Link**, give it a name such as "Newsletter, May". Only you see the name. Unnamed links show as "Link 1", "Link 2", and so on. 4. **Set optional limits** Open **Appearance**, **Limits**, or **Query parameters** if you need them. Changes save as you type. ![The Public link sheet: the link picker with Create new link, the share link, the QR Code, Embed, and Popup cards, and the Link, Appearance, Limits, and Query parameters sections](https://formstep.io/docs/images/building-forms/share-panel.png) The Public link sheet, from the link icon at the top of the form page. ### Link settings - On Pro and Business, a **Custom URL** puts the link on one of your [custom domains](https://formstep.io/docs/branding-domains/custom-domains/), at an address like forms.yoursite.com/feedback. The slug has 3 to 64 lowercase letters, numbers and hyphens. - The **Default language** is the language people see when they arrive through this link. They can still switch. - With **Set expiration date**, the link stops at the end of that day, in your local time. Visitors then see “Link expired”. - **Limit response slots** caps how many responses this link accepts. See [Response limits](https://formstep.io/docs/sharing-publishing/scheduled-close/#response-limits). Give each place you share the form its own link, such as one for your newsletter and one for social media, or one in French and one in English. You can then [compare them in Analytics](https://formstep.io/docs/submissions-analytics/analytics-insights/#comparing-share-links). ### Revoking a link Click **Revoke** at the bottom of the sheet. The link stops taking responses and shows “Link revoked”. Its responses stay. Click **Re-activate** to turn it back on. Moving a form to [Trash](https://formstep.io/docs/workspaces-teams/trash/) revokes all its links; after a restore, re-activate each one. ### Deleting a link Open the **⋯** menu at the bottom of the sheet and click delete. A link without responses is removed. A link with responses is revoked instead, so its responses and analytics stay. ## UTM tags for campaign tracking UTM tags tell Analytics where a visitor came from. Add them to a share link you post somewhere, and the visit shows under that source in the [Traffic Sources](https://formstep.io/docs/submissions-analytics/analytics-insights/#traffic-source-classification) chart: - `utm_source` says where the traffic comes from, such as facebook or newsletter. - `utm_medium` says how it arrives, such as social, email or cpc. It sets the category in Traffic Sources. - `utm_campaign` names the campaign, such as may-launch. ``` https://forms.yoursite.com/feedback?utm_source=linkedin&utm_medium=social&utm_campaign=product-launch ``` Without UTM tags, Formstep uses the site the visitor came from, which some apps hide. UTM values are not saved with each submission. To save one, add a [hidden field](https://formstep.io/docs/building-forms/hidden-fields/) with the same name, such as utm\_source. It then shows in the submissions table too. ### Add them without editing the URL The **Query parameters** section of the Public link sheet lists the three UTM tags and every hidden field on the form. Values you fill in are added to the copied link and the QR code, but not to embed and popup snippets. ## QR codes In the Public link sheet, pick a link and click the **QR Code** card, then **Download PNG** (512 × 512 px). The code opens the same URL as **Copy link**. Use one link per placement, such as one for a poster and one for packaging. Scan a test print before you print many. ## Social preview A link posted in chat or on social media shows a preview with the form’s title and description. To change the title, description, image and icon, open **Appearance**, **Share link preview** in the Public link sheet. On a [custom domain](https://formstep.io/docs/branding-domains/custom-domains/), you can also set defaults for all forms on the domain, and hide forms from search engines. Forms on formstep.io are always hidden from search engines. > **Test your link** > > Open the link in a private window to see what respondents see. ## Next steps [Embedding & popups](https://formstep.io/docs/sharing-publishing/embedding-popups/) — Embed or show your form as a popup [Your own domains](https://formstep.io/docs/branding-domains/custom-domains/) — Serve forms from your own domain [Share link analytics](https://formstep.io/docs/submissions-analytics/analytics-insights/#comparing-share-links) — Compare performance per link [Response limits](https://formstep.io/docs/sharing-publishing/scheduled-close/#response-limits) — Cap how many responses a link accepts # Embedding & popups Embed your form on a website or show it as a popup over the page. Pick the options in the Public link sheet, then paste one snippet into your site. Each snippet uses the share link selected in the sheet, with its expiry, response limit, custom domain and analytics. Query parameter values are not included. See [Share links](https://formstep.io/docs/sharing-publishing/sharing-embedding/). ## Inline embed An inline embed shows the form inside your page. 1. Open the **Public link** sheet and click the **Embed** card. 2. Set the height and choose what to hide. 3. Click **Copy embed code** and paste it into your site’s HTML. With **Auto-resize height** on, the embed grows with the form, so respondents never scroll inside it. Some site builders remove the small script this adds; then turn it off and set a height that fits. **Lazy load** waits to load the form until the visitor scrolls to it, which speeds up a page where the form sits further down. ## Popup A popup shows the form on top of your page. 1. Open the **Public link** sheet and click the **Popup** card. 2. Choose how it opens, where it sits and how it looks. 3. Click **Copy popup code** and paste it into your site. Under **Trigger**, pick how the popup opens. **Automatically on page load** opens it once the page has loaded, after an optional delay, and **Show once per session** stops it from opening again on every page of the same visit. **When a function is called** opens it from your own button or link: the comment in the copied code names the function to call, and the [site builder pages](#site-builders) show a button that calls it. **Auto-close after submission** closes the popup three seconds after the respondent submits. It is off by default. ## Color scheme Embeds and popups have the same color scheme setting, and it overrides the form’s own theme inside the frame. The default, **Let user specify**, follows the visitor’s light or dark preference. **Dark mode** and **Light mode** always use that mode. > **Match your site's theme** > > If your website is always dark, pick Dark mode so the form blends in. To theme the form itself, see [Appearance & theming](https://formstep.io/docs/branding-domains/appearance-theming/). ## Step by step for your site builder [Webflow](https://formstep.io/docs/sharing-publishing/embed-in-webflow/) — A Code Embed element [WordPress](https://formstep.io/docs/sharing-publishing/embed-in-wordpress/) — A Custom HTML block [Framer](https://formstep.io/docs/sharing-publishing/embed-in-framer/) — The Embed component [Notion](https://formstep.io/docs/sharing-publishing/embed-in-notion/) — An Embed block, from the form's address ## Next steps [Share links](https://formstep.io/docs/sharing-publishing/sharing-embedding/) — Create and manage share link URLs [QR codes](https://formstep.io/docs/sharing-publishing/sharing-embedding/#qr-codes) — Generate and place scannable codes [Your own domains](https://formstep.io/docs/branding-domains/custom-domains/) — Serve forms from your own domain [Appearance & theming](https://formstep.io/docs/branding-domains/appearance-theming/) — Theme, cover, logo, dark mode # Embed a form in Webflow Put a Formstep form inside a Webflow page with a Code Embed element, or open it as a popup from a button. Which Webflow plans allow it, and what the Designer shows. The [Code Embed element](https://help.webflow.com/hc/en-us/articles/33961332238611-Custom-code-embed) is available on a Workspace on Core, Growth, Agency or Freelancer, or on a site with an active Site plan. ## 1. Copy the embed code In Formstep, open the **Public link** sheet, click the **Embed** card, then **Copy embed code**. ## 2. Add a Code Embed element 1. In the Webflow Designer, open the **Add** panel and add a **Code Embed** element where the form should sit. 2. Paste the code. 3. Save and close the code editor. The embed is as wide as the element it sits in. Its height is the one you set in Formstep. ## What the Designer shows With **Auto-resize height** off, Webflow previews the form on the canvas. With it on, the code holds a script, and the Designer shows a placeholder. The script runs once you preview or publish the site, so check the height on the published page. ## Open the form as a popup Click the **Popup** card in the Public link sheet and copy the popup code. Paste it into a Code Embed element anywhere on the page. The Designer shows a placeholder for it. With the trigger **Automatically on page load**, nothing more is needed. To open it from a button, add the button to the same Code Embed, with the function name from the comment in your code: ``` ``` > **If the form does not show** > > Look at the published page: a script never runs on the canvas. If the Add panel has no Code Embed element, the Workspace or site plan does not include it. ## Next steps [Embedding & popups](https://formstep.io/docs/sharing-publishing/embedding-popups/) — Every embed and popup setting [Appearance & theming](https://formstep.io/docs/branding-domains/appearance-theming/) — Make the form look like your site # Embed a form in WordPress Put a Formstep form inside a WordPress page or post with a Custom HTML block. What WordPress.com plans allow, and why WordPress may remove the code when you save. You need a WordPress user who may post unfiltered HTML. On a single site you host yourself, that is an Administrator or an Editor. On WordPress.com, you need a paid plan with hosting features activated. See [If WordPress removes the code](#code-removed). ## 1. Copy the embed code In Formstep, open the **Public link** sheet, click the **Embed** card, then **Copy embed code**. ## 2. Add a Custom HTML block 1. Open the page or post in the block editor. 2. Click the **Block Inserter** and search for HTML, or type `/html` in a new paragraph and press Enter. 3. Click **Edit HTML**, paste the code, and click **Update**. The preview beside the code shows the form. Before WordPress 7.0 the block is one text box: paste the code straight into it. 4. Save or publish the page. WordPress describes the block on its [Custom HTML block page](https://wordpress.org/documentation/article/custom-html/) and [WordPress.com’s page](https://wordpress.com/support/wordpress-editor/blocks/custom-html-block/). ## Open the form as a popup Click the **Popup** card in the Public link sheet and copy the popup code. Paste it into a Custom HTML block on the page. With the trigger **Automatically on page load**, nothing more is needed. To open it from a button, add the button to the same block, with the function name from the comment in your code: ``` ``` ## If WordPress removes the code WordPress removes the embed and popup code in two cases: - Your role may not post unfiltered HTML, as with a Contributor. WordPress strips the code when such a user saves. Ask an Administrator to add the block. - On WordPress.com, the plan has no hosting features activated. A paid plan alone is not enough; WordPress.com’s help page says installing one plugin activates them. Then paste the code again. > **If the form does not grow** > > If the form shows but does not grow with its content, turn **Auto-resize height** off in Formstep and set a height that fits your form. ## Next steps [Embedding & popups](https://formstep.io/docs/sharing-publishing/embedding-popups/) — Every embed and popup setting [Appearance & theming](https://formstep.io/docs/branding-domains/appearance-theming/) — Make the form look like your site # Embed a form in Framer Put a Formstep form inside a Framer page with the Embed component, from the share link or from the embed code. ## 1. Copy the embed code In Formstep, open the **Public link** sheet and click the **Embed** card. Leave **Auto-resize height** off: in Framer you size the Embed component on the canvas, and the form scrolls inside it. Click **Copy embed code**. ## 2. Add an Embed component 1. In Framer, open the **Insert** menu and search for **Embed**. 2. Drag the Embed component onto the canvas, where the form should sit. 3. Paste the code into the **HTML** section of the component. 4. Size the component on the canvas, then preview or publish the site. ### Or use the link The Embed component also takes a URL. Use the address inside the embed code, the part in quotes after `src=`. It is your share link with `?embed=1` at the end, which leaves out the page around the form: ``` https://form.formstep.io/Abc12XYz?embed=1 ``` > **Popups in Framer** > > The popup code is a script, and [Framer’s help page](https://www.framer.com/help/articles/how-to-add-an-iframe-or-embed-script/) says a script may need extra code around it to run in an Embed component. The inline embed needs none. ## Next steps [Embedding & popups](https://formstep.io/docs/sharing-publishing/embedding-popups/) — Every embed and popup setting [Appearance & theming](https://formstep.io/docs/branding-domains/appearance-theming/) — Make the form look like your site # Embed a form in Notion Put a Formstep form inside a Notion page with an Embed block. Notion takes a link, not HTML, so you paste the form address with ?embed=1. ## 1. Copy the form’s embed address In Formstep, open the **Public link** sheet, click the **Embed** card, choose what to hide, and click **View code**. Copy only the address inside the code, the part in quotes after `src=`. It starts with `https`: ``` https://form.formstep.io/Abc12XYz?embed=1 ``` It is your share link with `?embed=1` at the end, which leaves out the page around the form. What you chose to hide is in the address too. ## 2. Add an Embed block 1. In Notion, type `/embed` on a new line, or click the **+** beside it and choose **Embed**. 2. Paste the address and confirm. 3. Hover over the embed and drag the bars at its sides to size it. Notion explains embeds on its [help page on embeds](https://www.notion.com/help/embed-and-connect-other-apps). ## What does not apply in Notion Height and auto-resize belong to the HTML code, which Notion does not take. The embed is the size you drag it to, and a longer form scrolls inside it. Popups do not work either: the popup code is a script, and a Notion page runs none. > **If the form does not show** > > Check that you pasted the address and not the whole code, and that the address opens the form in a browser tab. ## Next steps [Embedding & popups](https://formstep.io/docs/sharing-publishing/embedding-popups/) — Every embed and popup setting [Notion integration](https://formstep.io/docs/integrations/notion/) — Send each submission to a Notion database # Authentication gate Require respondents to sign in or enter a password before they open your share link, and pick the gate that fits the form. Requests skip both. Both gates apply to share links only. A [request](https://formstep.io/docs/requests/creating-requests/) goes to one recipient on their own link, so it skips them. ## Require sign-in Open **Settings**, the **Public link access** tab, and turn on **Require sign-in**. People then sign in with Google or a link sent to their email before they can open the form. - Their email is saved with the submission. - Unfinished answers belong to their account, so they can continue on another device. - Each person can submit once across all their devices, unless you allow more under [Limits per respondent](https://formstep.io/docs/sharing-publishing/scheduled-close/#per-respondent-limits). Use it for internal forms, where every answer should carry a verified company email, for customer-only forms, and for forms where anonymous answers are too risky. Sign-ups and contact forms lose answers when people have to sign in, so use [bot protection](https://formstep.io/docs/sharing-publishing/captcha-bot-protection/) on those. The gate verifies the person who opens the form. To verify an email address typed into an Email question, use [Respondent email verification](https://formstep.io/docs/building-forms/email-verification/), which sends a one-time code and works without sign-in. ## Password Turn on **Password** in **Settings**, **Public link access**, and type a password. Visitors see “Password required” and none of the questions until they enter it. Anyone with the password can submit. > **Turn on the switch and set a password together** > > If the switch is on but no password is saved, publishing warns you, and the form stays open to everyone. ### Changing the password You can change the password at any time. People already filling in the form can finish; new visitors need the new password. To stop one password from being reused without end, add a [response limit](https://formstep.io/docs/sharing-publishing/scheduled-close/#response-limits) to the share link. A limit of 1 makes the link single-use. ## Next steps [Bot protection (Turnstile)](https://formstep.io/docs/sharing-publishing/captcha-bot-protection/) — Block automated submissions on share links [Close a form or cap responses](https://formstep.io/docs/sharing-publishing/scheduled-close/) — Close on a date, or cap responses per share link # Bot protection (Turnstile) Defend share links against bot submissions with Cloudflare Turnstile. When it runs, who can turn it off, and the extra setup a custom domain needs. ## How it works When a respondent submits, Turnstile checks in the background that they are not a bot. In rare cases a small dialog asks them to confirm, with no image puzzles. A submission that fails the check is rejected, and the respondent can try again. Requests skip the check, because a [request](https://formstep.io/docs/requests/overview/) link belongs to one recipient. ## Forms on formstep.io On `form.formstep.io` links, bot protection needs no setup. - On Free, it is always on. - On Pro and Business, turn it on or off per form in **Settings**, **Public link access**, **Spam protection**. If your plan lapses, it turns back on after 30 days. See [Upgrading & downgrading](https://formstep.io/docs/subscription-billing/upgrading-downgrading/#grace-period). ## Only if you use a custom domain On a [custom domain](https://formstep.io/docs/branding-domains/custom-domains/) (Pro and Business), such as `fill.yourdomain.com`, bot protection needs your own free Turnstile keys, because Formstep’s key only covers formstep.io. If all your forms use formstep.io links, skip this section. 1. **Add your custom domain first** The Bot Protection card on the Domains page appears once the workspace has a custom domain. 2. **Create a Cloudflare account** Sign up for a free account at [dash.cloudflare.com](https://dash.cloudflare.com) if you don't have one. 3. **Create a Turnstile widget** In [Turnstile](https://dash.cloudflare.com/?to=/:account/turnstile), add a widget with your custom domains as allowed hostnames. 4. **Copy your keys** Copy the **Site key** and **Secret key**. 5. **Paste the keys in Formstep** Open **Domains** in the sidebar, find the **Bot Protection** card, paste both keys, and save. They apply to the whole workspace. 6. **Turn it on per form** Turn on **Spam protection** in each form's **Settings**, **Public link access**. Without keys, bot protection cannot run on the custom domain: | Owner | Custom domain without keys | | - | - | | Pro or Business | Submissions go through without a check. | | Free, guest, or a plan lapsed past day 30 | Submissions on the branded URL are rejected. The formstep.io link keeps working. | ## Next steps [Authentication gate](https://formstep.io/docs/sharing-publishing/authentication-gate/) — Ask for sign-in or a password [Your own domains](https://formstep.io/docs/branding-domains/custom-domains/) — Set up branded form URLs # Close a form or cap responses Stop a form taking answers now or on a date, cap how many responses one share link accepts, and limit how often one person can submit. ## Close the form Open **Settings** (the gear icon) and the **Submissions** tab. Under **Accepting submissions**, pick **Closed** to stop taking answers now, or **Until a date** to close the form on its own on that date. Until then, the form’s status badge reads _Closes_ with the date. To reopen the form, pick **Open**. ![The Submissions tab of form settings: Accepting submissions with Open, Until a date, and Closed, then Delete submissions, After someone submits, and Let people change their answers](https://formstep.io/docs/images/building-forms/settings-submissions.png) Settings, Submissions tab. ### What closing does The form stays published and its links keep working, but: - Visitors see “This form is closed”. - People who already started cannot submit either. - Reminders for unfinished answers stop. - You cannot create new [requests](https://formstep.io/docs/requests/creating-requests/), and request links show the closed page. ## Cap responses on a share link Each share link has its own response limit. Open **Public link** at the top of the form page, pick a link, open **Limits**, and turn on **Limit response slots**. Enter the number of responses, at least 1. Use it to cap a workshop registration at 30 seats, or give each link its own cap, such as 50 for your newsletter and 200 for social media. The form as a whole has no cap, so to stop it at a count, set a limit on every active link. ### When a slot is taken A slot is taken when someone starts the form, not when they submit, so 200 people cannot all start a 30-seat registration and lose their answers at the end. If someone starts and leaves, their slot comes back after about an hour. A submitted response keeps its slot. ### What respondents see When a link is full, new visitors see “Response limit reached”. People who already started can still submit. Your other share links stay open. For “the first 100 people, and only until Friday”, set both. The date closes the whole form; the limit closes only that share link. ## Limits per respondent By default, each person submits once. To let people answer again, open **Settings**, **Submissions**, and under **After someone submits** pick **Offer another response**. It shows a **Submit again** button, and **Maximum responses per respondent** caps how often one person can submit. The cap is strict for signed-in respondents. For anonymous ones, Formstep remembers them in their browser, so a new device gets around it. If the count matters, use the [authentication gate](https://formstep.io/docs/sharing-publishing/authentication-gate/). To limit how often someone can change a sent answer, see [Edit after submit](https://formstep.io/docs/submissions-analytics/edit-after-submit/). ## Next steps [Share links](https://formstep.io/docs/sharing-publishing/sharing-embedding/) — Create a link per channel [Redirect after submit](https://formstep.io/docs/building-forms/custom-thank-you/#redirect-instead) — Send respondents to a custom URL on completion --- ## Submissions & Analytics # Submissions & Analytics overview Every answer to a form arrives on its Submissions tab, from share links and requests alike. Read it there, take it out, or get an email when it arrives. ## Where answers arrive Every answer to a form arrives on the form’s **Submissions** tab, whether it came through a share link or a request. The tab has three views: **List** shows one row per submission, **Summary** adds up the answers per question, and **Analytics** shows how many people open, start and finish the form. An answer to a request also shows on that request, on the [Requests](https://formstep.io/docs/requests/overview/) page. ![A form's Submissions tab in the List view: one row per submission with when it was sent, who sent it, whether it came through the public link or a request, and the answers.](https://formstep.io/docs/images/introduction/submissions.png) The Submissions tab: answers from the public link and from requests in one list. ## Download, connect or get an email Download answers as CSV or Excel, or one submission as a PDF. To send each new answer to another tool on its own, connect an [integration](https://formstep.io/docs/integrations/overview/). To hear about it, turn on an email to yourself when someone submits, and a confirmation to the respondent. | Feature | Plan | | - | - | | List, Summary, exports, email to yourself | Every plan | | Analytics, partial submissions, confirmation to the respondent | Pro and Business | | Submission retention | Business | ## Pages in this section - [Submission inbox](https://formstep.io/docs/submissions-analytics/submission-inbox/) — Every submission to a form, from share links and requests alike, in one table where you search, filter, open, download, or delete them. - [Aggregated summary](https://formstep.io/docs/submissions-analytics/question-summary/) — See how everyone answered each question, with counts, charts and drop-off, find the questions people leave at, and get a short AI summary of the answers. - [Translating a submission](https://formstep.io/docs/submissions-analytics/translating-submissions/) — Read an answer written in a language you do not speak, translated in place with AI credits. - [Edit after submit](https://formstep.io/docs/submissions-analytics/edit-after-submit/) — Let respondents return and update their answers after submitting. - [Analytics & insights](https://formstep.io/docs/submissions-analytics/analytics-insights/) — See how many people open, start, and finish each form, where they come from, and where they leave. - [Exporting submissions](https://formstep.io/docs/submissions-analytics/exports/) — Download submissions as CSV or Excel in bulk, or one submission as a PDF. - [Partial submissions](https://formstep.io/docs/submissions-analytics/partial-submissions/) — Formstep saves what people enter before they leave a form. - [Email notifications](https://formstep.io/docs/submissions-analytics/self-notifications/) — Get an email each time someone submits your form, and on Pro and Business send the respondent a confirmation, with your own text and a PDF of the answers. - [Submission retention](https://formstep.io/docs/submissions-analytics/submission-retention/) — Delete the submissions of a form automatically after a number of days or on a date, together with the requests they answered. # Submission inbox Every submission to a form, from share links and requests alike, in one table where you search, filter, open, download, or delete them. ## Getting there Open a published form from the Forms list and pick the **Submissions** tab at the top. From the editor, **Inbox** brings you there. ## The three views **List** shows one row per response. **Summary** shows charts and counts per question; see [Aggregated summary](https://formstep.io/docs/submissions-analytics/question-summary/). **Analytics** shows views, completion rate, devices and traffic sources on Pro and Business; see [Analytics & insights](https://formstep.io/docs/submissions-analytics/analytics-insights/). Press `1`, `2`, or `3` to switch views. Answers to test requests are not shown. For requests still waiting on their recipient, see the form’s [Requests tab](https://formstep.io/docs/requests/managing-requests/#form-requests). ## Table columns The table shows when each response came in, the respondent’s name or email (or “Anonymous”) under **Submitter**, and under **Channel** whether it came through the public link or a request. Then comes one column per question, hidden field and calculated field. ![The List view on the Submissions tab, with Submitted at, Submitter, Channel, and answer columns](https://formstep.io/docs/images/introduction/submissions.png) The List view on the Submissions tab. Click a header to sort. The sliders icon in the toolbar shows, hides, and reorders columns, including the hidden-by-default **Submission language** and PIN columns. To get a column for a UTM tag, see [UTM tags](https://formstep.io/docs/sharing-publishing/sharing-embedding/#utm-parameters). ### Row actions The ⋮ button on a row opens: - **View submission** shows every answer in a side sheet. - [Translate submission](https://formstep.io/docs/submissions-analytics/translating-submissions/) shows the answers in your language. - **Download PDF** saves the response in the respondent’s language. Unfinished responses have none. - On rows that came from a request, **Open request** opens it. - **Delete submission** removes it with its files, signatures and payment details. This cannot be undone. ## Search and filters Search finds any text in any answer. The **Status** filter shows **Completed** responses by default; on Pro and Business it also offers **All** and [Partial](https://formstep.io/docs/submissions-analytics/partial-submissions/). When the form has two or more share links, a **Share link** filter lets you [compare them](https://formstep.io/docs/submissions-analytics/analytics-insights/#comparing-share-links). ## Repeating groups and bookings A [repeating group](https://formstep.io/docs/building-forms/repeating-groups/) is one column with its number of items. Open the submission to see each item as a numbered card. A [Cal.com booking](https://formstep.io/docs/integrations/cal-com/) shows its event, time, attendee, and meeting link under its question. ## Download all The download button above the table offers **Download as CSV** and **Download as Excel**, with your current filters applied. See [Exporting submissions](https://formstep.io/docs/submissions-analytics/exports/). ## Live updates New submissions appear without a refresh. Click **Live data** to pause updates while you read. The table shows 50 rows per page; while you search or sort, **Load all** loads the rest. ## Next steps [Aggregated summary](https://formstep.io/docs/submissions-analytics/question-summary/) — All responses combined, with one block per question [Analytics & insights](https://formstep.io/docs/submissions-analytics/analytics-insights/) — Charts, conversion rates, and traffic data [Exporting submissions](https://formstep.io/docs/submissions-analytics/exports/) — Download CSV, Excel or PDF [Partial submissions](https://formstep.io/docs/submissions-analytics/partial-submissions/) — Recover responses that never finished # Aggregated summary See how everyone answered each question, with counts, charts and drop-off, find the questions people leave at, and get a short AI summary of the answers. ## Open the summary Open a form’s [Submissions](https://formstep.io/docs/submissions-analytics/submission-inbox/) tab and click **Summary**. ![The Summary view: a matrix question as a grid of shares per row, with a Table and List switch, and a ranking question with each option's average place and how many put it first](https://formstep.io/docs/images/submissions-analytics/summary.png) The Summary view, one block per question. ## AI summary At the top of the Summary view, the AI summary recaps what the answers add up to: a headline and up to three points, such as which questions go together and where people split. Only your team sees it here. ![The AI summary block at the top of the Summary view: a headline on the left, three points on the right, and above them Based on 40 responses, the time it was generated, 2 credits and a Generate again button](https://formstep.io/docs/images/submissions-analytics/summary-ai.png) The AI summary at the top of the Summary view. - Press **Generate summary**. It reads the responses from the form’s share links and uses about 2 [AI credits](https://formstep.io/docs/ai/ai-credits-usage/). - The summary never updates on its own. When new responses come in, the block says how many, and **Generate again** writes a new one from all of them. - AI reads only the totals. It never sees a single response, a name or a written answer. - To show the summary to respondents as well, switch it on in the form’s [live results](https://formstep.io/docs/building-forms/live-results/#ai-summary). ## What each block shows | Metric | What it tells you | | - | - | | Responses | How many people answered. | | Viewed | How many people reached the question. Pro and Business. | | Drop-off | How many saw the question and left. Pro and Business. | | Answers | A chart or list, depending on the question type. The switch at the top right of a block changes the view. | Viewed and drop-off are recorded only while the workspace owner is on Pro or Business. A question with a high drop-off is the one people leave at: change it, publish, and compare the next responses. ## Charts by question type | Question type | Shown as | | - | - | | Radio, Checkboxes, Dropdown, picture choice | A bar per option with its share and count, or a table | | Star Rating | The average, then a bar per star count, or a list | | Linear Scale | The average, then a column per step with an average marker, or a list | | Ranking | Options from best to worst, with the average place and how many put each first, or a list | | Payment | Totals per currency, or a list of payments | | Toggle Switch | The share of Yes and No, and how many left it blank | | Matrix / Grid | A grid with the share of each column per row, or a list of answers | | Text, Email, Phone, URL, Number, Date, Time | The latest 50 answers | | File Upload, Signature | Counts only | A [repeating group](https://formstep.io/docs/building-forms/repeating-groups/) is one block with its number of items and a small block per question. ## Filter and arrange Search filters questions by title, and the **Status** and **Share link** filters work as in the List view. The sliders icon hides and reorders questions. In a chart, the option most people picked gets the full color. A matrix grid adds up to 100% per row, and a stronger tint means more people chose it, as the Fewer and More legend under the grid shows. The summary counts the latest 5,000 submissions. Export to count every response. ## Export the summary The download button exports the summary as CSV or Excel. See [Exporting submissions](https://formstep.io/docs/submissions-analytics/exports/#summary-export). ## Next steps [Analytics & insights](https://formstep.io/docs/submissions-analytics/analytics-insights/) — Views, completions and bounce rate per form [Completion funnel](https://formstep.io/docs/submissions-analytics/analytics-insights/#completion-funnel) — Where people leave, from opening the form to submitting [Partial submissions](https://formstep.io/docs/submissions-analytics/partial-submissions/) — Recover responses that never finished [Repeating groups](https://formstep.io/docs/building-forms/repeating-groups/) — Let respondents add as many items as they need # Translating a submission Read an answer written in a language you do not speak, translated in place with AI credits. The original answers stay as they were, and exports use them. This works on every plan, including Free. [Compare plans](https://formstep.io/pricing/) ## Translate one In the Submissions table, open a row’s **⋮** menu and choose **Translate submission**. On a request’s page, click **Translate** above the answers. When it is done, switch between **Show translation** and **Show original**. The original answers never change, and exports use them. ## Which language Answers are translated into the workspace’s **Notification email language**, set in Workspace Settings. If it is not set, they are translated into the language you use Formstep in. See how this fits with the rest of a multilingual form in [the guide on the blog](https://formstep.io/blog/multilingual-forms/). ## What is translated Formstep translates written answers and chosen option labels. Dates, numbers, ratings, emails, phone numbers, URLs, files, signatures, bookings, and payments stay as they are. Answers already in your language are not translated. ## What it costs Translation uses [AI credits](https://formstep.io/docs/ai/ai-credits-usage/), from your own balance or the workspace’s shared credits. Translating the same submission again is free, unless the respondent changed their answers. With no credits, Formstep does not translate. A translation is deleted with its submission, and [retention](https://formstep.io/docs/submissions-analytics/submission-retention/) applies to it too. ## Next steps [Submission inbox](https://formstep.io/docs/submissions-analytics/submission-inbox/) — Read and manage responses [AI credits & usage](https://formstep.io/docs/ai/ai-credits-usage/) — What each AI action costs [Multilingual forms](https://formstep.io/docs/building-forms/translating-form-content/) — Offer the form in several languages [Exporting submissions](https://formstep.io/docs/submissions-analytics/exports/) — Download CSV, Excel or PDF # Edit after submit Let respondents return and update their answers after submitting. Turn it on in Settings, pick how many edits to allow, and check which forms cannot offer it. ## Turn it on Open **Settings**, the **Submissions** tab, and turn on **Let people change their answers**. Next to the switch, pick **Maximum edits**: 1, 2, or 3. With the setting off, a submitted answer is final. ## How it works After submitting, respondents see **Edit your response** on the thank-you page. It opens the form with their answers, and submitting again updates the same submission. The button works only in the browser they submitted from. ## When people cannot edit - A completed request is final. Send a new request for a correction. - A form with a Payment, Signature, or Schedule appointment question allows no edits, and publishing turns the setting off. - A verified email answer (Business) cannot be changed. See [Respondent email verification](https://formstep.io/docs/building-forms/email-verification/). - A form that goes to a web page after submit shows no edit button. If you change the form, a returning respondent sees the latest version. Answers to questions that still exist are kept. ## Next steps [Submission inbox](https://formstep.io/docs/submissions-analytics/submission-inbox/) — See edited responses appear in your inbox [Limits per respondent](https://formstep.io/docs/sharing-publishing/scheduled-close/#per-respondent-limits) — Let people send another response # Analytics & insights See how many people open, start, and finish each form, where they come from, and where they leave. Filter by channel, date range, device, or share link. This needs the Pro plan or higher. [Compare plans](https://formstep.io/pricing/) ## Open analytics Open a form’s **Submissions** tab and click **Analytics**, or press `3`. > **Visits are recorded on Pro and Business** > > Formstep records visits only while the workspace owner is on Pro or Business, from the day you upgrade. Request numbers are on the form’s [Requests tab](https://formstep.io/docs/requests/managing-requests/#overview) instead, on every plan. ## The numbers | Metric | What it counts | | - | - | | Views | Times the form was opened, including repeat visits | | Unique Visitors | Different people who opened it | | Engaged | Visitors who clicked, typed, or picked an answer | | Submissions | Completed responses | | Completion | Submissions divided by Engaged | | Avg. Time | Time from the first answer to submit | | Bounce Rate | Visitors who left without answering anything | A refresh does not count again. The same person on two devices counts twice. ## Charts - Activity Over Time shows views, engaged visitors and submissions per day. - The [Completion Funnel](#completion-funnel) goes from Viewed to Engaged to Completed. - Traffic Sources splits visits into direct, email, social, search and referral. Click a bar to see the sites and campaigns, and see [how sources are classified](#traffic-source-classification). - Device, browser and location breakdowns show what people use and where they are. The country is an estimate. - Peak Activity Hours shows when people submit, by hour of the day. ## Filters Filter by channel (public link or requests), by date range (the last 7, 30 or 90 days, or all time), and by device (desktop, mobile or tablet). When the form has two or more share links, you can also pick one or more of them. Filters combine, for example the last 7 days on mobile for one share link. Click **Live data** to pause updates. ## Completion funnel The funnel shows where you lose people. A big drop from Viewed to Engaged means the start looks like too much work, the purpose is unclear, or a password or sign-in puts people off. A big drop from Engaged to Completed points to a hard or sensitive question, or a form that is too long; the [Summary](https://formstep.io/docs/submissions-analytics/question-summary/) view shows which question people leave at. Wait for 50 to 100 submissions before you change the form based on the funnel. ## Compare share links Give each channel its own [share link](https://formstep.io/docs/sharing-publishing/sharing-embedding/), such as one for your newsletter and one for social media, and give it a clear name. Then pick one link at a time in the **Share link** filter to compare them. The filter works in the List and Summary views too. Revoked links stay in the filter. To test two versions of a form, duplicate it, change one copy, and give each its own share link. ## How traffic sources are classified Formstep reads the link’s [UTM tags](https://formstep.io/docs/sharing-publishing/sharing-embedding/#utm-parameters) first. Without them, it uses the site the visitor came from. | Category | When | | - | - | | Email | utm\_medium mentions email or newsletter | | Social | utm\_medium mentions social, or the source is a social network such as LinkedIn (in utm\_source or the site the visitor came from) | | Search | utm\_medium mentions cpc, paid or search, or the source is a search engine such as Google (in utm\_source or the site the visitor came from) | | Referral | Any other UTM tag or site | | Direct | No UTM tag, and no site or only your own | Some apps hide the site a visitor came from. Add UTM tags to the links you post there. ## How long data is kept Analytics are kept for 365 days. Submissions are not affected. To delete a form’s analytics, open **Settings**, **Danger zone**, **Delete analytics data**. A view shows at most the latest 10,000 events; narrow the date range for older ones. ## Next steps [Aggregated summary](https://formstep.io/docs/submissions-analytics/question-summary/) — Find the question people leave at [Partial submissions](https://formstep.io/docs/submissions-analytics/partial-submissions/) — Recover responses that never finished [Share links](https://formstep.io/docs/sharing-publishing/sharing-embedding/) — One link per channel [Exporting submissions](https://formstep.io/docs/submissions-analytics/exports/) — Download CSV, Excel or PDF # Exporting submissions Download submissions as CSV or Excel in bulk, or one submission as a PDF. Each format holds different content, and your filters decide what goes in. ## Where to export from In the Submissions tab’s **List** or **Summary** view, click the download button above the table and choose **Download as CSV** or **Download as Excel**. For one response as a PDF, use **Download PDF** in the row’s ⋮ menu. ## Formats CSV suits spreadsheets and importing into other tools. Excel keeps data types such as numbers and dates. A PDF is a printable copy of one response, with images and signatures. ## Bulk export CSV and Excel include the responses and columns the table shows, with your filters, search, and column order. Answers to test requests are never included. > **Export partials for follow-up** > > Set the status filter to **Partial** before you export to get the unfinished responses. ## Per-submission PDF The PDF is in the respondent’s language. The first download takes a few seconds. Unfinished responses have no PDF. ### What the PDF contains The PDF is the form, filled in with this respondent’s answers: - the cover and logo - every block, in form order, with images, signatures, and booking details - “Submitted at”, and the Formstep badge unless you turned branding off (Pro) The PDF shows the form as the respondent saw it, so editing the form later does not change it. ### PDF as an email attachment On Pro and Business, turn on **Attach submission PDF** in **Settings**, **Emails**, to send the PDF with an [email notification](https://formstep.io/docs/submissions-analytics/self-notifications/). If the PDF is not ready within 15 minutes, the email goes without it. ## What’s included by format | Content | CSV and Excel | PDF | | - | - | - | | Answers | The columns the table shows | Every block, in form order | | Files, images and signatures | A link | Images and signatures shown, files listed | | Payments | Amount and receipt link | Amount | | Translated submissions | The original answers | The respondent's language | ## Repeating-group data in exports A [repeating group](https://formstep.io/docs/building-forms/repeating-groups/) is one column, with its items separated by a pipe: ``` Full name: Jane Appleseed, Age: 34 | Full name: Marcus Lee, Age: 29 | Full name: Priya Shah, Age: 41 ``` Blank answers show `-`. ## Summary export Exporting from the **Summary** view gives totals per question instead of responses: counts and percentages per option, averages for ratings, and totals per currency for payments. ## Automating exports There are no scheduled exports. To send every new response to a spreadsheet, connect [Google Sheets](https://formstep.io/docs/integrations/google-sheets/) or [Airtable](https://formstep.io/docs/integrations/airtable/). ## Next steps [Partial submissions](https://formstep.io/docs/submissions-analytics/partial-submissions/) — Recover responses that never finished [Google Sheets](https://formstep.io/docs/integrations/google-sheets/) — Add each submission to a spreadsheet as a row [Airtable](https://formstep.io/docs/integrations/airtable/) — Add each submission to an Airtable base [Repeating groups](https://formstep.io/docs/building-forms/repeating-groups/) — Let respondents add as many items as they need # Partial submissions Formstep saves what people enter before they leave a form. See where they stop, and use reminders to bring back the ones who gave an email. This needs the Pro plan or higher. [Compare plans](https://formstep.io/pricing/) ## When a partial is captured Formstep saves a partial as soon as the respondent answers something, and updates it as they go. Opening the form without answering saves nothing. [Requests](https://formstep.io/docs/requests/overview/) save partials too, and the request’s page shows the answers so far. ## Viewing partials In the Submissions tab, set the status filter to **Partial**, or **All** for both. The filter also works in Summary, Analytics, and exports. ## What gets saved A partial holds every answer so far, including files, and the link or request it came through. A partial has no PDF, and its signatures stay hidden until the respondent submits. ## What to do with partials Partials show the question where people stop and leave. If a partial has an email address, [reminders](https://formstep.io/docs/requests/invitations-and-reminders/#reminders) can bring that lead back. ## Send unfinished drafts to an integration Most [integrations](https://formstep.io/docs/integrations/overview/) can also receive partials: turn on **Unfinished drafts** when you set one up, and pick how long a draft must sit untouched first, from 12 hours to a week. Formstep checks once an hour and sends each draft once. Only drafts from a share link are sent; an unfinished request stays on the Requests page. ## Privacy and retention Partials and their files are deleted 30 days after they were last saved. See [Submission retention](https://formstep.io/docs/submissions-analytics/submission-retention/). ## Next steps [Reminders](https://formstep.io/docs/requests/invitations-and-reminders/#reminders) — Email people who did not finish [Email notifications](https://formstep.io/docs/submissions-analytics/self-notifications/) — Get an email when submissions arrive [Compare share links](https://formstep.io/docs/submissions-analytics/analytics-insights/#comparing-share-links) — See which channel people finish from [Exporting submissions](https://formstep.io/docs/submissions-analytics/exports/) — Download CSV, Excel or PDF # Email notifications Get an email each time someone submits your form, and on Pro and Business send the respondent a confirmation, with your own text and a PDF of the answers. Both emails are set up in the form’s **Settings**, on the **Emails** tab. Click **Preview** to see one. On Pro and Business, write your own subject and body with [@ mentions](https://formstep.io/docs/building-forms/answer-piping/), or leave them empty for Formstep’s default email. You can also send from [your own email domain](https://formstep.io/docs/branding-domains/custom-domains/#custom-email-domains), so the emails come from your address and are less likely to land in spam. ![Form settings on the Emails tab, with Notify on new submission switched on](https://formstep.io/docs/images/submissions-analytics/settings-email-notifications.png) Settings, Emails tab. ## An email to you Turn on **Notify on new submission**. On Free, the email goes to the workspace owner, with a link to the submission. On Pro and Business, you can add more recipients, or a recipient taken from an answer. The email is in the workspace’s **Notification email language**. > **Pair with integrations** > > For team alerts without more email, add the [Slack](https://formstep.io/docs/integrations/slack/) or [Discord](https://formstep.io/docs/integrations/discord/) integration. ## A confirmation to the respondent On Pro and Business, turn on **Send confirmation to respondent**. In **To**, pick the Email question that holds the respondent’s address. On a request, the email goes to the recipient, even if the form asks for no email. The email uses the language the respondent answered in; see [Translating form content](https://formstep.io/docs/building-forms/translating-form-content/). Use it to confirm an application and say what happens next, to repeat a booked date and time, or to give a reference number with the **6 Digit PIN** mention. ## PDF of the answers On Pro and Business, turn on **Attach submission PDF** on either email to include the answers as a PDF, for example to send a calculated price as a quote. See [what the PDF contains](https://formstep.io/docs/submissions-analytics/exports/#per-submission-pdf). ## Reminders **Send reminders**, on the same tab, emails people who started but did not finish, and request recipients who have not answered. See [Reminders](https://formstep.io/docs/requests/invitations-and-reminders/#reminders). ## Next steps [Slack](https://formstep.io/docs/integrations/slack/) — Post each submission to a Slack channel [Send emails from your domain](https://formstep.io/docs/branding-domains/custom-domains/#custom-email-domains) — Send from your own address # Submission retention Delete the submissions of a form automatically after a number of days or on a date, together with the requests they answered. This needs the Business plan. [Compare plans](https://formstep.io/pricing/) ## Set it up Open **Settings**, the **Submissions** tab. Under **Delete submissions**, pick: | Option | What it does | | - | - | | Never | Submissions stay until you delete them. The default. | | After | Each submission is deleted when it is this many days old. | | On a date | Every submission of the form is deleted on that date, including ones that arrive later. | Deletion runs once a day. A rule you set keeps running if you downgrade, because it is a promise to your respondents. You can clear it on any plan. ## What is deleted Deletion removes the answers, uploaded files, signatures, PDFs, and the request the submission answered, with everything the request held. It cannot be undone. To keep a copy, [export](https://formstep.io/docs/submissions-analytics/exports/) first, or connect [Google Sheets](https://formstep.io/docs/integrations/google-sheets/). ## Requests A completed, expired, or canceled request is deleted when its time passes the window, with its answers, files, documents, recipient details, and history. A pending request is never deleted. You can also [delete a request](https://formstep.io/docs/requests/managing-requests/#delete) yourself. ### Requests that ended without an answer An expired or canceled request without answers is deleted 30 days after it ended, on every plan, whether or not you set a rule. ## Partial submissions Unfinished responses and their files are deleted 30 days after their last save, on every plan. A rule with a date also deletes partials. Forms that sit unused on Free follow a separate rule; see [Limits & quotas](https://formstep.io/docs/subscription-billing/limits-quotas/#inactive-forms). ## Next steps [Exporting submissions](https://formstep.io/docs/submissions-analytics/exports/) — Download responses before they are deleted [Partial submissions](https://formstep.io/docs/submissions-analytics/partial-submissions/) — Recover responses that never finished [Limits & quotas](https://formstep.io/docs/subscription-billing/limits-quotas/) — Every number, plan by plan [Workspaces overview](https://formstep.io/docs/workspaces-teams/overview/) — Manage who can access your forms and data --- ## Integrations # Integrations overview Every tool Formstep works with and the page that sets each one up: where answers go, where requests come from, what runs inside the form, analytics, and your own site. ## Where the answers go - [HubSpot](https://formstep.io/docs/integrations/hubspot/) — Update or create the HubSpot contact behind each submission, matched by email. - [Google Sheets](https://formstep.io/docs/integrations/google-sheets/) — Add every submission to a Google spreadsheet, with the columns you choose. - [Airtable](https://formstep.io/docs/integrations/airtable/) — Push submissions into an Airtable base with typed field mapping. - [Notion: submissions](https://formstep.io/docs/integrations/notion/) — Add submissions to a Notion database, with each field mapped to a property. - [Slack](https://formstep.io/docs/integrations/slack/) — Post new submissions to a Slack channel with custom message templates. - [Discord](https://formstep.io/docs/integrations/discord/) — Post each form submission to a Discord channel with mentions. - [Linear](https://formstep.io/docs/integrations/linear/) — Open a Linear issue from each submission, in the team you pick. - [GitHub](https://formstep.io/docs/integrations/github/) — Open a GitHub issue from each submission, in the repository you pick. - [Webhooks](https://formstep.io/docs/integrations/webhooks/) — Send each submission to a URL of your own, such as your backend, a serverless function or an automation tool that takes webhooks. - [n8n: submissions](https://formstep.io/docs/guides/n8n/public-link-submissions/) — Run an n8n workflow when someone submits, edits or abandons a form through its share link, and pass the answers to the next node. - [Zapier: submissions](https://formstep.io/docs/guides/zapier/public-link-submissions/) — Start a Zap when someone submits, edits or abandons a form through its share link, and send the answers to email, Sheets, Slack or a CRM. - [Make: submissions](https://formstep.io/docs/guides/make/public-link-submissions/) — Run a Make scenario when someone submits, edits or abandons a form through its share link, and map each answer by its question. ## Where requests come from - [n8n: requests](https://formstep.io/docs/guides/n8n/send-a-request/) — Create a Formstep request from an n8n workflow, prefill and lock what you already know, then wait for the answer in the same workflow or start another one when a request ends. - [Zapier: requests](https://formstep.io/docs/guides/zapier/send-a-request/) — Create a Formstep request from any Zapier trigger, prefill and lock the answers you already know, send the link, then find, remind or cancel the request from a later Zap. - [Make: requests](https://formstep.io/docs/guides/make/requests/) — Create a Formstep request from a Make scenario, prefill it per field, and run a scenario when the recipient completes it, it expires or is canceled. - [MCP](https://formstep.io/docs/guides/ai-agents/send-a-request/) — Ask a connected agent to send a Formstep request to one person, prefill and lock what you already know, and read the answers back when they are done. - [Claude, ChatGPT](https://formstep.io/docs/guides/ai-agents/connect/) — Add Formstep to Claude, ChatGPT, Cursor or any tool that supports remote MCP servers, sign in, and pick the workspace the agent may use. - [REST API](https://formstep.io/docs/developers/quickstart-send-a-request/) — Create an API token, read a form's field keys, and create a prefilled request with curl, from any tool that can make an HTTP call. ## Inside the form - [Cal.com](https://formstep.io/docs/integrations/cal-com/) — Let respondents choose and book a Cal.com appointment without leaving your form. - [Stripe](https://formstep.io/docs/building-forms/payment-collection/) — Accept one-time payments from respondents via Stripe. ## Analytics - [Google Analytics, Axiom](https://formstep.io/docs/integrations/analytics-streaming/) — Send form views, starts and submissions to Google Analytics 4 or Axiom, and, with Axiom, the answers of each submission too. ## On your own site - [Inline embed, Popup](https://formstep.io/docs/sharing-publishing/embedding-popups/) — Embed your form on a website or show it as a popup over the page. - [Webflow](https://formstep.io/docs/sharing-publishing/embed-in-webflow/) — Put a Formstep form inside a Webflow page with a Code Embed element, or open it as a popup from a button. - [WordPress](https://formstep.io/docs/sharing-publishing/embed-in-wordpress/) — Put a Formstep form inside a WordPress page or post with a Custom HTML block. - [Framer](https://formstep.io/docs/sharing-publishing/embed-in-framer/) — Put a Formstep form inside a Framer page with the Embed component, from the share link or from the embed code. - [Notion: embed](https://formstep.io/docs/sharing-publishing/embed-in-notion/) — Put a Formstep form inside a Notion page with an Embed block. ## A form’s integrations Open a form and click **Integrations** (plug icon) in its top bar. The dialog lists every integration, says how each one on this form is doing, and opens the one you pick to set it up, test it or change it. An amber dot on the button means an integration stopped delivering: see [Health and monitoring](https://formstep.io/docs/integrations/health-monitoring/). ![The Integrations dialog: every integration on the left, Google Sheets and Slack with their status, and the Slack integration open on the right: its status with Send test, Recent deliveries, and its setup read back with Edit.](https://formstep.io/docs/images/integrations/integrations-dialog.png) A form's Integrations dialog Every integration works on every plan. Sending unfinished drafts to an integration needs Pro or Business. > **Send only what you need** > > An integration sends respondents’ answers to another service. Connect only destinations you trust, map only the fields you need, and make sure your privacy notice covers it. # HubSpot Update or create the HubSpot contact behind each submission, matched by email. Map fields to contact properties, and pick contacts as recipients of a request. ## What you get - One HubSpot contact per email address, updated or created - Each field mapped to a contact property of a matching type - Edits update the same contact again - A legal basis for new contacts, for accounts that need one - A test send that changes only a test contact For an overview, see [Formstep for HubSpot](https://formstep.io/integrations/hubspot/). ## Connect HubSpot 1. **Open Integrations** Click **Integrations** (plug icon) in the form's top bar, then pick HubSpot under CRM. 2. **HubSpot account** Pick a connected HubSpot account, or click **Connect HubSpot account**. Formstep then loads the account's contact properties. 3. **Contact properties** Match each form field to a contact property. Only compatible properties are listed. Map one form field to Email: Formstep finds the contact by it. If the account needs a legal basis, pick one here too. 4. **Turn it on** Click **Turn on HubSpot**, then **Send test** to write sample values to the test contact formstep-test\@example.com. ## How a submission finds its contact The answer mapped to **Email** picks the contact. On each completed submission, Formstep: 1. Updates the HubSpot contact with that email, writing only the properties you mapped. 2. Creates the contact when no contact has that email. A new contact gets the mapped answers, the email, and the legal basis. When a respondent edits their submission, Formstep updates the same contact again. An empty answer never clears a value in HubSpot. Formstep never changes properties you did not map. > **Make the email question required** > > Without an email answer, the delivery fails and is not retried. Make the question you mapped to Email required. ## Legal basis for new contacts With data privacy settings on, HubSpot creates no contact without a legal basis. EU accounts have this setting by default. Then the Configure step shows **Legal basis for new contacts** with your account’s options. Pick one to continue. Formstep writes the legal basis only on a new contact. An existing contact keeps its legal basis, because it records the consent your team collected. You cannot map a form field to the legal basis. ## Property types | Form field | Compatible HubSpot properties | | - | - | | Any field | Single-line text, Multi-line text | | Number, Rating, Scale | Number, plus text | | Date | Date picker, Date and time picker, plus text | | Phone | Phone number, plus text | | Radio, Dropdown, Checkboxes, Picture choice | Dropdown select, Radio select, Multiple checkboxes, plus text | | Toggle switch | Single checkbox, plus text | | Repeating group (whole group) | Text only | - A choice answer goes to the HubSpot option whose internal value or label matches the Formstep option’s key or label, ignoring case. An answer with no matching option is left out, and the rest of the write goes through. - Several selected options go to a Multiple checkboxes property together. - Metadata can be mapped as well, for example **Submitted at** to a date property or **PDF Link** to a text property. Read-only, calculated, hidden, and archived properties are not listed. The property list refreshes each time you open the integration. ## Test event **Send test** writes placeholder values to the contact `formstep-test@example.com`, and creates it if needed. It never changes any other contact. Delete the test contact in HubSpot when you are done. ## Find HubSpot contacts for a request The New request sheet can also add people from HubSpot. Click **Import**, then **Find in HubSpot**, and search by name or email. A contact fills in the person’s email and name. Fill in the rest of the [request](https://formstep.io/docs/requests/overview/) as usual. On Pro and Business, tick several contacts to send one request each: see [Bulk send](https://formstep.io/docs/requests/bulk-send/). - The form needs no HubSpot integration for this. A contact you pick never fills in form fields. - The workspace owner connects HubSpot once, under Integrations or from **Import** › **Connect HubSpot**. Then everyone in the workspace can search it. When the connection expires, the owner sees **Reconnect HubSpot** there. - Answers reach HubSpot only through the form’s HubSpot integration, like every other submission. - **Try it yourself** never updates HubSpot: a test request does not reach integrations. > **Only in the dashboard** > > An automation cannot search HubSpot through Formstep. It looks the contact up itself, for example with the HubSpot step in n8n or Zapier, and prefills the request with what it found. Prefill and lock the field mapped to Email with the contact’s email, and the answers update that contact. See [Prefill, locked fields and hidden fields](https://formstep.io/docs/requests/creating-requests/#three-buckets). ## What it doesn’t do - Unfinished drafts are not sent to HubSpot. Only finished submissions and edits update contacts. - Existing submissions are not exported when you connect. Contacts are updated from the next submission on. - It writes contacts only, not companies, deals, or tickets. ## Property changes in HubSpot - Delete a mapped property: Formstep leaves it out, and the rest of the contact still updates. - Change a property’s type or options: answers are converted to the new definition. - Change a property to a type that no longer fits the form field: the mapping shows a warning. Map that field again. ## When something goes wrong - If the integration shows **Connection expired**, for example because the app was uninstalled in HubSpot, click **Reconnect**. - HubSpot can refuse a value, for example one that breaks a validation rule on the property. The failed event under **Recent events** shows HubSpot’s message. Fix the form field or the property, then send it again with [Retry all](https://formstep.io/docs/integrations/health-monitoring/#failed-deliveries). - If the contact properties do not load, click **Try again**. If it keeps failing, reconnect the account. ## FAQ **What access does Formstep need?** Access to read and write contacts and to read their property definitions. You sign in with HubSpot, so there are no keys to manage. It does not ask for access to companies, deals or other objects. **Can other workspace members see my HubSpot connection?** Yes. HubSpot accounts are connected to the workspace. Any member can use any connected account in an integration. **Can I connect multiple HubSpot accounts?** Yes. The workspace owner connects more accounts, from Integrations or during setup. Each form picks one account. **Does an edit create a second contact?** No. The edit updates the contact with the email in the submission. If the respondent changes the email answer, Formstep updates or creates the contact with the new email. **How do I remove the integration?** Open Integrations, pick HubSpot under **On this form**, and choose **Delete integration** in the ⋯ menu. To also revoke Formstep’s access, uninstall the Formstep app in HubSpot under Settings › Integrations › Connected apps. ## Next steps [Send a request](https://formstep.io/docs/requests/creating-requests/) — Prefill and lock fields for one recipient [Health and monitoring](https://formstep.io/docs/integrations/health-monitoring/) — Retries, pausing and reconnecting [Client intake template](https://formstep.io/templates/client-onboarding/) — A form to prefill from a contact, whose answers update it [Email capture to a CRM-prefilled form](https://formstep.io/workflows/zapier-hubspot-prefill-form/) — A Zap that looks the person up in HubSpot and sends them a prefilled form # Google Sheets Add every submission to a Google spreadsheet, with the columns you choose. Formstep creates the spreadsheet, and past submissions can be copied in too. ## What you get - One row per submission, as it arrives - The columns you choose, with your own headers - A spreadsheet Formstep creates for you - Past submissions copied in when you connect ## Connect Google Sheets 1. **Open Integrations** Click Integrations in the form's top bar, then pick Google Sheets. 2. **Google account and spreadsheet** Pick a connected Google account, or click **Connect Google account**. You can name the spreadsheet; it defaults to the form name. 3. **Columns** All fields are included by default. Turn off **Export all form fields** to remove any, add metadata, and rename column headers. 4. **Turn it on** Under **More options**, turn on **Export existing submissions** to copy past responses. Click **Turn on Google Sheets**, then **Send test** to add a test row. ## Column mapping Toggle **Export all form fields** to include every question, or turn it off to pick specific fields and set custom column headers. Formstep writes values by column position. Renaming a header in Google Sheets is fine; moving or deleting a column puts values in the wrong place. When your mapping changes, Formstep rewrites the header row. ### Repeating groups Map a [repeating group](https://formstep.io/docs/building-forms/repeating-groups/) in either or both ways: - The whole group maps to one column. Every item shows as numbered, labeled rows, like “1. Full name: Jane Appleseed, Age: 34, 2. Full name: Marcus Lee, Age: 29”. - Each member field maps to its own column. The values from every item join with `· `, so the Full name column reads “Jane Appleseed · Marcus Lee”. ### Metadata columns | Column | What it contains | | - | - | | Submitted at | The date and time the response was submitted. | | Submitter | The respondent's email address when one was captured, otherwise "Anonymous". | | Submission ID | A unique identifier for the submission. | | Form name | The internal name of your form. | | Submission status | "Completed", "Partial" for an unfinished draft, or "Updated" when the respondent edited it. | | PDF Link | A link to the generated submission PDF. | | Submission language | The language the response was submitted in, as a code such as de. | | Channel | "Public link" when the response came through a share link, "Request" when it answered a request. | | Request ID | The id of the request the response answered. Empty for a share-link response. | By default, Submitted at and Submitter are included. Add or remove any metadata column during setup, and rename any of them like a normal column header. ## Auto-created spreadsheets Formstep creates the spreadsheet in your Google Drive when the first submission arrives. If you delete it, Formstep creates a new one on the next submission. ## Test event **Send test** adds a row of placeholder values, so you can check the columns. ## Backfill existing submissions Past submissions are copied in the background, oldest first. The integration shows the progress. ## Unfinished drafts On Pro and Business, Google Sheets can also add [unfinished drafts](https://formstep.io/docs/submissions-analytics/partial-submissions/#unfinished-drafts), set under **More options** › **Unfinished drafts**. Their rows say “Partial” in the Submission status column. ## When something goes wrong | Problem | What happens | | - | - | | Google access ends | You'll be asked to reconnect your Google account. | | Spreadsheet deleted | Formstep creates a new spreadsheet with the header row and adds the submission, so nothing is lost. | | Permission denied | The Google account lost access to the spreadsheet. Reconnect the account. | | Sheet tab renamed | New rows stop with an error. Rename the tab back, or set up the integration again. | | Google is busy or down | The delivery is retried automatically. | For retries and pausing, see [Health and monitoring](https://formstep.io/docs/integrations/health-monitoring/). ## FAQ **Can other workspace members see my Google connection?** Yes. Google accounts are connected at the workspace level. Any member can use any connected account when setting up an integration, without re-authorizing it. **What access does Formstep need?** Permission to create spreadsheets in Google Drive and edit only the ones it creates (per-file access), and read your email and profile name. Formstep cannot read your other Drive files or access anything outside the spreadsheets it creates. **Can I connect multiple Google accounts?** A workspace can connect several Google accounts, and each form picks one. The workspace owner adds accounts during setup. **Can I edit the column mapping later?** Yes. Open the integration and re-map fields. The next delivery rewrites the header row to match; existing rows keep the values they were written with. **Can I point the integration at a different spreadsheet?** No. The spreadsheet is fixed once the integration is created. Delete it and create a new one. **How do I remove the integration?** Open Integrations, pick Google Sheets under **On this form**, and choose **Delete integration** in the ⋯ menu. To also revoke Formstep’s access to your Google account, visit your [Google account permissions](https://myaccount.google.com/permissions) page. ## Next steps [Airtable](https://formstep.io/docs/integrations/airtable/) — Add each submission to an Airtable base [Notion](https://formstep.io/docs/integrations/notion/) — Add each submission to a Notion database [Repeating groups](https://formstep.io/docs/building-forms/repeating-groups/) — Let respondents add as many items as they need [Exporting submissions](https://formstep.io/docs/submissions-analytics/exports/) — Download CSV, Excel or PDF # Airtable Push submissions into an Airtable base with typed field mapping. Each submission becomes a new record, and past submissions can be copied in when you connect. ## What you get - One record per submission - Each field mapped to a column of a matching type - Metadata columns, including a link to the submission PDF - An optional rich-text body built from the answers - Past submissions copied in when you connect - Mappings that keep working when you rename a column ## Connect Airtable 1. **Open Integrations** Click **Integrations** (plug icon) in the form's top bar, then pick Airtable. 2. **Account and table** Pick a connected Airtable account, or click **Connect Airtable account** and choose the bases Formstep may use. Then pick the base and table. 3. **Fields** Match each form field to a column. Only columns of a compatible type are listed. You can also write a rich-text body. 4. **Turn it on** Under **More options**, turn on **Export existing submissions** to copy past responses. Click **Turn on Airtable**, then **Send test** to write a placeholder record. ## Column types | Form field | Compatible Airtable columns | | - | - | | Short / long text | Single line text, Long text, Rich text | | Email | Email, Single line text, Long text, Rich text | | Phone | Phone, Single line text, Long text, Rich text | | URL | URL, Single line text, Long text, Rich text | | Number | Number, Currency, Percent, Rating, Duration, Single line text, Long text, Rich text | | Rating / Scale | Rating, Number, Single line text, Long text, Rich text | | Date | Date, Date & time, Single line text, Long text, Rich text | | Time | Single line text, Long text, Rich text | | Toggle switch | Checkbox, Single line text, Long text, Rich text | | Radio / Dropdown (single) | Single select, Multi-select, Single line text, Long text, Rich text | | Checkboxes (multi) | Multi-select, Single line text, Long text, Rich text | | File upload | Attachments, URL, Single line text, Long text, Rich text | | Signature | URL, Single line text, Long text, Rich text | | Picture choice | Multi-select, Single select, Single line text, Long text, Rich text | | Payment | Currency, Number, Single line text, Long text, Rich text | | Matrix / Ranking | Single line text, Long text, Rich text | When an answer matches no select option, Formstep creates the option. You cannot map read-only columns: formula, rollup, count, lookup, linked record, collaborator, created/modified time, created/modified by, auto-number, button, and synced-source columns. ## Repeating groups Map a [repeating group](https://formstep.io/docs/building-forms/repeating-groups/) in either or both ways: - The whole group maps to one field. Every item shows as numbered, labeled rows, like “1. Full name: Jane Appleseed, Age: 34, 2. Full name: Marcus Lee, Age: 29”. - Each member field maps to its own field. The values from every item join with `· `, so the Full name field reads “Jane Appleseed · Marcus Lee”. ## Rich-text body You can write the content of a Rich text, Long text, or Single line text column in the template editor. Type **@** to insert an answer or metadata, such as PDF Link. Rich text columns keep the formatting. Other columns get plain Markdown. You can use headings (H1-H4), **bold**, _italic_, ~~strikethrough~~, `inline code`, links, and lists. Images and embeds are not supported. ## Test event **Send test** writes a record of placeholder values. Select columns get the question’s first option, so no new options are created. Delete the test record when you are done. ## Backfill existing submissions Past submissions are copied in the background. The integration shows the progress. ## Unfinished drafts On Pro and Business, Airtable can also add [unfinished drafts](https://formstep.io/docs/submissions-analytics/partial-submissions/#unfinished-drafts), set under **More options** › **Unfinished drafts**. To tell them apart, map Submission status to a single-select or text column: it reads “Partial”, “Completed” or “Updated”. ## Column changes in Airtable - Rename a column: the mapping updates on the next submission. - Delete a column: Formstep drops it from the mapping and syncs the other fields. - Change a column to an incompatible type: that value fails to sync. Open the integration and map the field again. ## When something goes wrong - If a column is missing from the list, check that you gave Formstep access to the base and that the column type can be written (see [Column types](#column-types)). - If the integration shows **Connection expired**, for example because you revoked access in Airtable, click **Reconnect**. - When Airtable is busy, Formstep slows down and retries on its own. For retries and pausing, see [Health and monitoring](https://formstep.io/docs/integrations/health-monitoring/). ## FAQ **What access does Formstep need?** It reads and writes records, reads the structure of your bases, and reads your email to know which account is connected. You sign in with Airtable, so there are no keys to manage. **Can other workspace members see my Airtable connection?** Yes. Airtable accounts are connected to the workspace. Any member can use any connected account in an integration. **Can I connect multiple Airtable accounts?** Yes. The workspace owner connects more accounts, from Integrations or during setup. Each form picks one account. **What happens if I delete the Airtable table?** The next submission fails with a “not found” error and the integration stops. You cannot change the base or table later. Delete the integration and create a new one for the new table. **How do I remove the integration?** Open Integrations, pick Airtable under **On this form**, and choose **Delete integration** in the ⋯ menu. To also revoke Formstep’s access, remove Formstep in your Airtable account settings. ## Next steps [Notion](https://formstep.io/docs/integrations/notion/) — Add each submission to a Notion database [Google Sheets](https://formstep.io/docs/integrations/google-sheets/) — Add each submission to a spreadsheet as a row [Repeating groups](https://formstep.io/docs/building-forms/repeating-groups/) — Let respondents add as many items as they need [Health and monitoring](https://formstep.io/docs/integrations/health-monitoring/) — Retries, pausing and reconnecting # Notion Add submissions to a Notion database, with each field mapped to a property. Each submission becomes a new page with an optional body, and past submissions can be copied in. ## What you get - One Notion page per submission - Each field mapped to a database property - An optional page body built from the answers - Past submissions copied in when you connect - Mappings that keep working when you rename a property ## Connect Notion 1. **Open Integrations** Click **Integrations** (plug icon) in the form's top bar, then pick Notion. 2. **Workspace and database** Pick a connected Notion account, or click **Connect Notion workspace**. Share the database with Formstep there: Formstep sees only the databases you share. Then pick the database. 3. **Properties** Match each form field to a Notion property. Only properties of a compatible type are listed. You can also write a page body. 4. **Turn it on** Under **More options**, turn on **Export existing submissions** to copy past responses. Click **Turn on Notion**, then **Send test** to create a placeholder page. ## Property types | Form field | Compatible Notion properties | | - | - | | Short / long text | Title, Rich text | | Email | Email, Title, Rich text | | Phone | Phone, Title, Rich text | | URL | URL, Title, Rich text | | Number / Rating / Scale | Number, Title, Rich text | | Date | Date, Title, Rich text | | Time | Title, Rich text | | Radio / Dropdown (single) | Select, Status, Title, Rich text | | Checkboxes (multi) | Multi-select, Title, Rich text | | Toggle switch | Checkbox, Title, Rich text | | File upload | Files, URL, Title, Rich text | | Signature | URL, Title, Rich text | | Picture choice | Multi-select, Title, Rich text | | Matrix / Ranking / Payment | Title, Rich text | ### Metadata properties | Metadata | Compatible Notion properties | | - | - | | Submitted at | Date, Title, Rich text | | Respondent email | Email, Title, Rich text | | Submission ID | Title, Rich text | | Form name | Title, Rich text | | Submission status | Status, Select, Title, Rich text | | PDF Link | URL, Files, Title, Rich text | | Submission language | Title, Rich text | | Channel | Select, Title, Rich text | | Request ID | Title, Rich text | Channel reads “Public link” or “Request”. Request ID holds the id of the request the response answered. Use them to separate the two kinds of response. ## Repeating groups Map a [repeating group](https://formstep.io/docs/building-forms/repeating-groups/) in either or both ways: - The whole group maps to one Rich text or Title property. Every item shows as numbered, labeled rows, like “1. Full name: Jane Appleseed, Age: 34, 2. Full name: Marcus Lee, Age: 29”. - Each member field maps to its own property. The values from every item join with `· `, so the Full name property reads “Jane Appleseed · Marcus Lee”. ## Rich page body You can write the page body in the template editor. Type **@** to insert an answer or metadata, such as PDF Link. You can use paragraphs, **bold**, _italic_, _underline_, and links. Headings, lists, and images show as plain paragraphs. ## Test event **Send test** creates a real page of placeholder values in your database. Delete the test page when you are done. ## Backfill existing submissions Past submissions are copied in the background. The integration shows the progress. ## Unfinished drafts On Pro and Business, Notion can also add [unfinished drafts](https://formstep.io/docs/submissions-analytics/partial-submissions/#unfinished-drafts), set under **More options** › **Unfinished drafts**. To tell them apart, map Submission status to a Status or Select property: it reads “Partial”, “Completed” or “Updated”. ## Property changes in Notion - Rename a property: the mapping updates on the next submission. - Delete a property: Formstep drops it from the mapping and syncs the other fields. - Change a property to an incompatible type, for example number to select: that value can fail to sync. Open the integration and map the field again. ## When something goes wrong - If a database is missing from the list, connect again and share the database with Formstep on Notion’s consent screen. - If a value does not sync, its property has a type that does not fit. Open the integration to see which field to fix. - If you remove Formstep in Notion, under Settings › Connections, the integration stops and the person who set it up gets an email. Connect again to restore it. - When Notion is busy, Formstep retries on its own. For retries and pausing, see [Health and monitoring](https://formstep.io/docs/integrations/health-monitoring/). ## FAQ **Does Formstep create the database for me?** No. Create the database in Notion first, then share it with Formstep during authorization. **What happens if I delete the Notion database?** The next submission fails with a “not found” error and the integration stops. You cannot change the database later. Delete the integration and create a new one for the new database. **Can other workspace members see my Notion connection?** Yes. Notion accounts are connected to the workspace. Any member can use any connected account in an integration. **Can I connect multiple Notion accounts?** Yes. The workspace owner connects more Notion workspaces, from Integrations or during setup. Each form picks one account. **How do I remove the integration?** Open Integrations, pick Notion under **On this form**, and choose **Delete integration** in the ⋯ menu. To also revoke Formstep’s access, remove Formstep in Notion, under Settings › Connections. ## Next steps [Airtable](https://formstep.io/docs/integrations/airtable/) — Add each submission to an Airtable base [Google Sheets](https://formstep.io/docs/integrations/google-sheets/) — Add each submission to a spreadsheet as a row [Repeating groups](https://formstep.io/docs/building-forms/repeating-groups/) — Let respondents add as many items as they need [Health and monitoring](https://formstep.io/docs/integrations/health-monitoring/) — Retries, pausing and reconnecting # Slack Post new submissions to a Slack channel with custom message templates. Insert answers with @, mention users and groups, and post to several channels per form. ## What you get - One message per submission, in the channel you pick - Your own message template. Type **@** to insert a field value. - Mentions of users, user groups, and `@here`, `@channel`, or `@everyone` - A separate message for unfinished drafts, on Pro and Business - Several Slack integrations per form, each with its own channel and template ## Connect Slack 1. **Open Integrations** Click Integrations in the form's top bar, then pick Slack. 2. **Workspace and channel** Pick a connected Slack workspace, or install the Formstep bot in a new one in Slack. Then pick a channel. Public channels show automatically. For a private channel, run /invite @formstep in it in Slack, then refresh the channel list. 3. **Message** Type @ to insert field values or to mention users, user groups, @here, @channel, or @everyone. Use bold, italic, or links. 4. **Unfinished drafts (optional)** Under **More options**, on Pro and Business, pick an idle window and write a message for unfinished drafts. Leave it Off to skip. 5. **Turn it on** Click **Turn on Slack**, then **Send test** to post a preview. Placeholders show as \[Question title], and no real answers are sent. ## Message template Type **@** in the template to insert an answer, submission details such as the PDF Link, a Slack user or user group to ping, or `@here`, `@channel` and `@everyone`. Bold, italic, strikethrough, inline code and links work in Slack; underline does not. A message can be up to 3,000 characters, and Formstep shortens a longer one. ## Unfinished drafts On Pro and Business, Slack can also post a separate message for [unfinished drafts](https://formstep.io/docs/submissions-analytics/partial-submissions/#unfinished-drafts) from the share link, set under **More options** › **Unfinished drafts**. ## When something goes wrong If the Formstep app is removed from Slack, reconnect it. If the channel is archived or deleted, delete the integration and set up a new one for another channel. For retries and pausing, see [Health and monitoring](https://formstep.io/docs/integrations/health-monitoring/). ## FAQ **Can I connect multiple Slack workspaces?** Yes. Each Slack workspace is a separate connection. The workspace owner adds one during a new integration setup. **Can other workspace members see my Slack connection?** Yes. Slack connections belong to the workspace. Any member can use any connected Slack workspace in an integration. **Can Slack post past submissions?** No. Slack posts only new submissions. For past data, use [Google Sheets](https://formstep.io/docs/integrations/google-sheets/) or [Notion](https://formstep.io/docs/integrations/notion/). **What permissions does the Formstep bot need?** Post messages, post to public channels without joining them, read channel lists, read user and group lists (for mentions), and read team info. It does not ask for admin permissions, message history, or file access. **How do I remove the integration?** Open Integrations, pick Slack under **On this form**, and choose **Delete integration** in the ⋯ menu. To remove the Formstep bot from Slack too, uninstall it on Slack’s app management page. ## Next steps [Discord](https://formstep.io/docs/integrations/discord/) — Post each submission to a Discord channel [Custom webhooks](https://formstep.io/docs/integrations/webhooks/) — Send each submission to your own URL # Discord Post each form submission to a Discord channel with mentions. Connect the Formstep bot, write your own message with answers in it, and send a test first. ## What you get Formstep posts each submission to the channel as it arrives. It connects as a bot, so you do not copy webhook URLs or IDs. Discord posts only new submissions. For past data, use [Google Sheets](https://formstep.io/docs/integrations/google-sheets/) or [Notion](https://formstep.io/docs/integrations/notion/). ## Connect Discord 1. **Open the integration** Click Integrations in the form's top bar, pick Discord, then click **Connect Discord account**. 2. **Server and channel** In the Discord window, authorize the Formstep bot, then pick the server. If the bot is not in it yet, click **Invite bot** and refresh. The channel list shows the text and announcement channels the bot can post in, grouped by category. 3. **Message** Type @ to insert field values, submission metadata such as PDF Link, Discord users, or roles. Use bold, italic, or links. 4. **Unfinished drafts (optional)** Under **More options**, on Pro and Business, pick an idle window and write a message for unfinished drafts. Leave it Off to skip. 5. **Turn it on** Click **Turn on Discord**, then **Send test** to post a preview. Placeholders show as \[Question title], and no real answers are sent. ## Unfinished drafts On Pro and Business, Discord can also post a separate message for [unfinished drafts](https://formstep.io/docs/submissions-analytics/partial-submissions/#unfinished-drafts) from the share link, set under **More options** › **Unfinished drafts**. ## Limits - A message can be up to 2,000 characters. Formstep shortens a longer one. - You can mention users and roles. `@everyone` and `@here` do not work, so mention a role instead. - Formstep posts to text and announcement channels only. - Bold, italic, strikethrough, inline code and links work; underline does not. ## When something goes wrong **The bot isn't in my server** Click **Invite bot** during setup. Pick the server and confirm the permissions. Then go back to Formstep and refresh the server list. **The integration stopped posting** Usually the bot was removed from the server, lost its permissions for the channel, or the channel was deleted. Open the integration in the form’s Integrations dialog: **Recent events** shows the error. Invite the bot again or reconnect, or delete the integration and set it up again. See [Health and monitoring](https://formstep.io/docs/integrations/health-monitoring/). ## FAQ **How do I change which channel messages go to?** You cannot change the server or channel after setup. Delete the integration and create a new one for the new channel. **Can I connect multiple Discord servers?** Yes. Each integration posts to one server and one channel. Add one Discord integration per server to the same form. **Can other workspace members see my Discord connection?** Yes. Discord connections belong to the workspace. Any member can use any connected Discord account in an integration. **What permissions does the Formstep bot need?** View Channels, Send Messages, Send Messages in Threads, Create Public Threads and Embed Links. It does not ask for Mention Everyone, Read Message History, Administrator, or Manage Messages or Channels. To limit the bot to one channel, set channel permission overrides for the Formstep bot role in Discord. **How do I remove the integration?** Open Integrations, pick Discord under **On this form**, and choose **Delete integration** in the ⋯ menu. To remove the bot from your server too, open Server Settings, then Integrations, in Discord and remove Formstep. ## Next steps [Slack](https://formstep.io/docs/integrations/slack/) — Post each submission to a Slack channel [Custom webhooks](https://formstep.io/docs/integrations/webhooks/) — Send each submission to your own URL # Linear Open a Linear issue from each submission, in the team you pick. Write the title and description yourself, insert answers with @, and copy past submissions in. ## What you get - One Linear issue per submission, created in your name - Title and description templates. Type **@** to insert a field value. - Markdown in the description: bold, lists, headings, code blocks, quotes, and links - An optional project for every issue - Past submissions copied in when you connect ## Connect Linear 1. **Open Integrations** Click Integrations in the form's top bar, then pick Linear. Click **Connect Linear workspace**, or pick an account the workspace already connected. 2. **Workspace and team** Issues go to this team. Each integration uses one team. Add another integration for a second team. 3. **Issue** Pick a project for every issue, if you like; it resets when you change the team. Then write the title and description: type @ to insert field values or submission metadata such as PDF Link. The title is plain text, up to 255 characters. The description is markdown. 4. **Unfinished drafts (optional)** Under **More options**, on Pro and Business, pick an idle window and write a title and description for unfinished drafts. Leave it Off to skip. 5. **Turn it on** Click **Turn on Linear**, then **Send test** to create a real issue with a \[Test] prefix in the team. Archive or delete it afterwards. ## Backfill existing submissions When you connect, you can copy past submissions in as Linear issues. Formstep creates them in the background, about one per second. ## Unfinished drafts On Pro and Business, Linear can also create a separate issue for [unfinished drafts](https://formstep.io/docs/submissions-analytics/partial-submissions/#unfinished-drafts) from the share link, set under **More options** › **Unfinished drafts**. These issues get a `partial-submission` label. ## Limits and behavior - One new issue per submission. Formstep does not comment on existing issues. - Formstep cuts a title longer than 255 characters and adds an ellipsis. - An empty title becomes `New submission to
`. - A file upload answer shows the file name and its download link. - A [repeating group](https://formstep.io/docs/building-forms/repeating-groups/) inserted with @ shows every item, joined by “ · “. ## When something goes wrong If you revoke Formstep’s access in Linear, the integration shows **Connection expired**: click **Reconnect**. For retries and pausing, see [Health and monitoring](https://formstep.io/docs/integrations/health-monitoring/). ## FAQ **What access does Formstep need?** Access to read your teams, projects and labels, and to create and update issues. Issues are created in your name. **Can I change the team or project after setup?** Yes. Open Integrations, pick Linear under **On this form**, change either field, and click Save. Changing the team resets the project, because projects belong to a team. **Can I connect multiple Linear workspaces?** Yes. The workspace owner connects each Linear account. Each integration picks one. **Can other workspace members see my Linear connection?** Yes. Linear connections belong to the workspace. Any member can use any connected Linear account in an integration. **How do I remove the integration?** Open Integrations, pick Linear under **On this form**, and choose **Delete integration** in the ⋯ menu. New submissions stop creating issues at once. To also revoke Formstep’s access, do it in your Linear user settings. ## Next steps [GitHub Issues](https://formstep.io/docs/integrations/github/) — Turn each submission into a GitHub issue [Health and monitoring](https://formstep.io/docs/integrations/health-monitoring/) — Retries, pausing and reconnecting # GitHub Issues Open a GitHub issue from each submission, in the repository you pick. Write your own title and body templates, and insert answers with @ mentions. ## What you get - One GitHub issue per submission, created in your name - Title and body templates. Type **@** to insert a field value. - GitHub-flavored markdown in the body: bold, lists, headings, code blocks, and links - An optional milestone for every issue ## Connect GitHub 1. **Open Integrations** Click Integrations in the form's top bar, then pick GitHub Issues. Click **Connect GitHub account**, or pick an account the workspace already connected. 2. **Account and repository** The list shows repositories you can push to. Archived and disabled repositories do not show. 3. **Issue** Pick a milestone for every issue, if you like; it resets when you change the repository. Then write the title and body: type @ to insert field values or submission metadata such as PDF Link. The title is plain text, up to 256 characters. The body is GitHub-flavored markdown. 4. **Unfinished drafts (optional)** Under **More options**, on Pro and Business, pick an idle window and write a title and body for unfinished drafts. Leave it Off to skip. 5. **Turn it on** Click **Turn on GitHub Issues**, then **Send test** to create a real issue with a \[Test] prefix in the repository. Close or delete it afterwards. ## Unfinished drafts On Pro and Business, GitHub Issues can also open a separate issue for [unfinished drafts](https://formstep.io/docs/submissions-analytics/partial-submissions/#unfinished-drafts) from the share link, set under **More options** › **Unfinished drafts**. These issues get a `partial-submission` label. ## Limits and behavior - One new issue per submission. Formstep does not comment on existing issues. - Only new submissions create issues. Past submissions are not copied in. - Formstep cuts a title longer than 256 characters and adds an ellipsis. - An empty title becomes `New submission to `. - A file upload answer shows the file name and its download link. - Answers go into the body as typed. An answer with `@username` notifies that GitHub user, and `#123` links to issue 123. ## When something goes wrong If you revoke Formstep’s access in GitHub, the integration shows **Connection expired**: click **Reconnect**. If the repository is archived or deleted, edit the integration and pick another repository. For retries and pausing, see [Health and monitoring](https://formstep.io/docs/integrations/health-monitoring/). ## FAQ **What access does Formstep need?** Access to create issues in your repositories, to see your account, and to list your organizations’ repositories. You sign in with GitHub; there is no GitHub App to install. **Can other workspace members see my GitHub connection?** Yes. GitHub connections belong to the workspace. Any member can use any connected GitHub account in an integration. **Can I change the repository or milestone after setup?** Yes. Open Integrations, pick GitHub Issues under **On this form**, change either field, and click Save. Changing the repository resets the milestone, because milestones belong to a repository. **How do I remove the integration?** Open Integrations, pick GitHub Issues under **On this form**, and choose **Delete integration** in the ⋯ menu. New submissions stop creating issues at once. This does not revoke Formstep’s access in GitHub: do that on your account’s authorized OAuth Apps page. ## Next steps [Linear](https://formstep.io/docs/integrations/linear/) — Turn each submission into a Linear issue [Health and monitoring](https://formstep.io/docs/integrations/health-monitoring/) — Retries, pausing and reconnecting # Custom webhooks Send each submission to a URL of your own, such as your backend, a serverless function or an automation tool that takes webhooks. ## How it works Each time someone submits the form, or edits a submission, Formstep sends the answers to your URL. A form can have several webhooks, and each one gets every submission. Unfinished drafts are not sent to a webhook. ## Add a webhook 1. **Open Integrations** Click **Integrations** in the form's top bar, then pick Webhook. 2. **URL** Type the https\:// address that should receive the answers. 3. **Answers to send** Leave the mapping empty to send every field. Add rows to send only some fields, each under a name you choose. Open **See an example request** to watch what is sent change as you edit. 4. **Signing secret and headers** Formstep creates a signing secret for this webhook. Copy it now and give it to whoever runs the receiving end. Add up to 5 headers of your own if the receiver needs them, such as an authorization token. 5. **Turn it on** Click **Turn on Webhook**, then **Send a test event** to send an example submission. > **Testing on your own computer** > > A webhook also accepts `http://localhost`, so you can try it against a receiver running on your machine. A request’s callback URL does not: it must use HTTPS. Formstep refuses addresses inside a private network. If a delivery fails, Formstep retries it and pauses the webhook after repeated failures: see [Health and monitoring](https://formstep.io/docs/integrations/health-monitoring/). ## What your URL receives Each delivery carries the form, the submission, and every answer twice: once as the stored value, under the question’s [field key](https://formstep.io/docs/requests/field-keys/), and once as text a person reads. To see the exact body for your form, open the webhook and expand **Example payload**. It uses your current mapping with sample answers. Each delivery is signed with the webhook’s secret, so the receiver can check it came from Formstep. Developers: see [Signing](https://formstep.io/docs/developers/webhooks-reference/#signing) in the webhooks reference, which also has the full format. ## FAQ **Can I change the signing secret?** No. Formstep creates it once, with the webhook. For a new secret, delete the webhook and add a new one. Only the workspace owner can see the secret later. **How do I get unfinished drafts at my URL?** A custom webhook sends only finished submissions and edits. A developer can subscribe to unfinished drafts through the [webhooks reference](https://formstep.io/docs/developers/webhooks-reference/#abandoned-submissions). Google Sheets, Airtable, Notion, Slack, Discord, Linear and GitHub Issues can send them too, on Pro and Business. **How do I remove the webhook?** Open Integrations, pick the webhook under **On this form**, and choose **Delete integration** in the ⋯ menu. Formstep stops sending at once and deletes the webhook’s event history. ## Next steps [Webhook API reference](https://formstep.io/docs/developers/webhooks-reference/) — Format, signing and retries, for developers [Airtable](https://formstep.io/docs/integrations/airtable/) — Add each submission to an Airtable base # Cal.com scheduling Let respondents choose and book a Cal.com appointment without leaving your form. Add a Schedule appointment question and get the booking with each submission. This works on every plan, including Free. [Compare plans](https://formstep.io/pricing/) ## What you get Schedule appointment is a question built into the form, so it uses your form’s theme and language. Cal.com supplies the event types, availability, calendar invitations, and meeting links. - Show or hide scheduling based on earlier answers. - Fill in the attendee name and email from earlier questions. - Show free times in the respondent’s time zone. - Get the booking details with the submission. ## Connect Cal.com 1. **Insert Schedule appointment** Type / in the editor, search for "Schedule appointment", and insert the question where the booking should appear. 2. **Connect Cal.com** Click **Connect Cal.com** in the question. Allow access in the Cal.com window, then return to the editor. 3. **Choose account and event type** Pick a connected Cal.com account, then select the event type respondents should book. 4. **Map attendee fields** Optionally set **Name from** and **Email from**. They list Text and Email questions above the scheduler, plus hidden fields. Leave them on "Do not autofill" to have respondents type both. 5. **Preview and publish** Test the booking in Preview. You cannot publish while the setup is incomplete. Cal.com accounts belong to the workspace. The workspace owner can also connect them under **Workspace › Integrations**, in **Payments & scheduling**. Every member can then pick one on any form. ## Configure the question | Setting | What it controls | | - | - | | Account | Which connected Cal.com account owns the event type. | | Event type | The Cal.com event type, duration, location, and availability respondents book. | | Name from | Optional earlier Text question or hidden field used to fill in the attendee name. | | Email from | Optional earlier Email question or hidden field used to fill in the attendee email. | Without these, respondents type their name and email in the question. ## Respondent experience 1. The respondent picks a date, then a free time. Times show in the browser’s time zone. The respondent can pick another in the **Time zone** list, and the booking uses it. 2. They confirm the attendee name and email, then click **Book**. 3. Cal.com creates the booking and sends its normal confirmation and calendar invitation. 4. Formstep shows the booking as **What**, **When**, **Who**, and **Where**, with the meeting link. 5. Before they submit, they can click **Cancel booking** and choose another time. > **Booking happens before form submission** > > Cal.com confirms the appointment when the respondent clicks **Book**. It joins the submission when they submit the form. Until then, the appointment stays on the host’s calendar, even if the respondent leaves. Formstep cancels it in these cases: > > - On a public link, the form is still not submitted 24 hours after booking. > - On a request, the request is canceled or expires. > - You delete the submission or the form. If someone else takes the time first, the respondent sees the times that are still free. If a booking fails halfway, Formstep cancels the calendar entry it created. A second device cannot book the same question twice. ## Use scheduling with logic Logic can show or hide Schedule appointment like any other question. For example, show booking only to leads with budget and authority. - A visible Schedule appointment question needs a confirmed booking before submission. A hidden one does not block submission. - After a booking, the answers that decide whether the question shows are locked. To change one, the respondent cancels the booking first. - If the question is hidden at submission anyway, for example because a branching rule skipped its page, Formstep cancels the booking on Cal.com. ## Publish checks You cannot publish while a Schedule appointment question is missing its account or event type. The error points to the question, and in the editor the question shows a **Not connected**, **Event missing** or **Configured** badge. A form can book only event types that need just a name, an email and a time. Event types that need confirmation, recur, have seats, cost money or have required booking questions show as **Not bookable from a form**, and you cannot select them. ## After submission The booking shows under the question in the submission, and in notification emails, PDFs, exports and integrations. Automations such as Zapier, Make and n8n get it as data too. Developers: see [Bookings and payments](https://formstep.io/docs/developers/webhooks-reference/#bookings-and-payments). The booking stays up to date after submission. When the respondent or host reschedules or cancels in Cal.com, or the host rejects it or marks a no-show, its text starts with **Rescheduled**, **Cancelled**, **Rejected** or **No-show**. Integrations are not sent again. Deleting a submission or the form cancels its appointments on Cal.com. Respondents cannot [edit their answers after submitting](https://formstep.io/docs/submissions-analytics/edit-after-submit/) a form with a Schedule appointment question, so the booking always matches the answers. ## Connection health Formstep keeps its Cal.com access fresh on its own and retries through short Cal.com outages, so neither needs anything from you. If access is revoked, the connection expires: - The question shows a **Disconnected** badge, and respondents cannot book. - The person who connected the account gets an email that lists each published form that books through it. - Reconnect Cal.com under **Workspace › Integrations**. Then select the account again and confirm the event type on each of those questions. When you disconnect a Cal.com account under **Workspace › Integrations**, Formstep lists the published forms that book through it. Those forms stop taking bookings, and their owner gets an email. Reconnect the same Cal.com account to restore them. You do not need to select anything again. ## Next steps [Conditional logic](https://formstep.io/docs/building-forms/conditional-logic/) — Reveal booking only for qualifying answers [Question types](https://formstep.io/docs/building-forms/field-types/) — See every question available in Formstep [Health and monitoring](https://formstep.io/docs/integrations/health-monitoring/) — Reconnect an account that expired [Submission inbox](https://formstep.io/docs/submissions-analytics/submission-inbox/) — View booking details with submissions # Analytics (GA4 and Axiom) Send form views, starts and submissions to Google Analytics 4 or Axiom, and, with Axiom, the answers of each submission too. Connect once for the workspace, then choose per form. This works on every plan, including Free. [Compare plans](https://formstep.io/pricing/) ## What is sent Formstep sends three events: when someone opens a form, when they first answer a question, and when they submit. Each event says which form it is, whether it came through a share link or a request, and, when Formstep knows them, the country, device, browser, UTM tags and referring site. A submit event also has how long the person took. Events carry a random visitor ID and never include answers, emails or question text. | Event | Sent when | Also carries | | - | - | - | | form\_view | Someone opens the form | form\_id, form\_name, source (link or request) | | form\_engaged | They first answer a question | The same | | form\_submit | They submit | The same, plus duration\_seconds | When known, every event also has country, device, browser, traffic\_source, traffic\_source\_name, traffic\_medium, traffic\_campaign and referrer\_host. Look for these names when you build a report in Google Analytics or query Axiom. Axiom can also receive the full answers of each submission. That stays off until you turn it on for a form. Google Analytics never gets answers. Events are sent once. If the destination does not respond, the event is dropped, and the person filling in the form never notices. Answers sent to Axiom are retried like any other integration. ## Choose per form Once a destination is connected, every form in the workspace sends events. To change that for one form, open it and click **Integrations** (plug icon) in its top bar. Under **Analytics streaming**, pick Google Analytics or Axiom: - Turn off **Forward analytics events** to stop events from this form. - Turn on **Send respondent answers** to send each submission’s answers to Axiom. - Click **Send test event** to check the connection without live traffic. ## Google Analytics (GA4) You need a GA4 property with a Web data stream. Only the workspace owner can connect it. 1. **Copy the Measurement ID** In Google Analytics, go to **Admin › Data streams**, open your Web stream and copy its ID, which looks like G-XXXXXXXXXX. 2. **Create an API secret** On the same stream, open **Measurement Protocol API secrets**, create a secret and copy its value. 3. **Connect in Formstep** Open **Integrations** in the workspace sidebar. Under **Analytics**, click **Connect** on Google Analytics, enter both values and click **Connect**. 4. **Send a test event** Open any form, click **Integrations**, pick Google Analytics and click **Send test event**. In GA4: - The test shows under **Reports › Realtime**. The events are named `form_view`, `form_engaged` and `form_submit`. - To report on a detail such as the form or the device, register it as a custom dimension. - Mark `form_submit` as a key event if a submission is a conversion for you. If the test is rejected, check that the Measurement ID and the API secret belong to the same Web stream. If nothing arrives, check your GA filters and internal-traffic rules. ## Axiom Only the workspace owner can connect Axiom. 1. **Create a dataset and a token** In Axiom, create or pick a dataset for Formstep, then create an API token that can ingest into it. 2. **Connect in Formstep** Open **Integrations** in the workspace sidebar. Under **Analytics**, click **Connect** on Axiom. Enter the dataset and the token, pick the region of your Axiom organization (US East 1 or EU Central 1), and click **Connect**. 3. **Send a test event** Open any form, click **Integrations**, pick Axiom and click **Send test event**. Each detail of an event arrives as its own field, so you can filter and group by form, source or device in your queries and dashboards. With **Send respondent answers** on, Axiom gets one record when someone submits and another when they edit their submission. Each record has the answers under each question’s [field key](https://formstep.io/docs/requests/field-keys/). Unfinished drafts are never sent. If the answers keep failing, Formstep stops sending them. Fix access to the dataset and save a new API token to start again. If the test is rejected, check the dataset name, that the token can ingest, and that the region matches your organization. Developers: the record format is the one in the [webhooks reference](https://formstep.io/docs/developers/webhooks-reference/#payload). ## Privacy and consent Analytics events have no answers, but their visitor ID and traffic details still count as personal data in many places. Tell respondents you use third-party analytics, ask for consent where the law requires it, and set how long GA4 or Axiom keeps the data. > **Answers in Axiom** > > Answer records can hold names, emails, free text, files and signatures. Before you turn them on, use a dataset only your team can read, give the token the least access it needs, set retention in Axiom, and make sure your privacy notice covers the transfer. ## Next steps [Analytics & insights](https://formstep.io/docs/submissions-analytics/analytics-insights/) — Views, starts and submissions inside Formstep [Health and monitoring](https://formstep.io/docs/integrations/health-monitoring/) — Retries and pausing for answer delivery # Health and monitoring What happens when an integration cannot deliver: how Formstep retries, when it pauses an integration, and how you turn it back on, retry failed deliveries or reconnect an account. ## Where you see a problem - The Integrations button in the form’s top bar shows an amber dot. - In the form’s Integrations dialog, the integration’s status line under **On this form** names the problem. - Open the integration and a banner says what happened and what to do. - The person who set up the integration gets an email, at most one per integration a day. ## What each status means | What you see | What it means | What to do | | - | - | - | | On | Submissions are sent as they come in. | Nothing. | | Paused | You turned it off, or Formstep paused it after repeated failures. | Fix the cause, then turn its switch back on. | | Connection expired | The account behind it no longer lets Formstep in. | Click **Reconnect** in the banner. | | Lost access to the destination | The connected account can no longer write to the spreadsheet, database or channel. | Share it with that account again, or reconnect with an account that has access. Then click **Reactivate**. | | Destination not found | The spreadsheet, database or channel no longer exists. | Delete the integration and set up a new one. A Linear team or GitHub repository can be changed in place instead. | | Delivery failed | Recent deliveries failed for another reason. | Read the error under **Recent events**, fix it, and click **Reactivate**. | ## Retries and failed deliveries When a delivery fails, Formstep tries it again on its own for about two hours. Most short outages at the destination clear up within that time without you noticing. If every try fails, the integration shows a banner such as “3 submissions failed to deliver”. Fix the cause, then click **Retry all** to send them again. Retry all also turns the integration back on if it was paused. Formstep keeps every submission whatever happens to an integration, so you can retry the failed ones later. ## When Formstep pauses an integration After 5 failed deliveries in a row, Formstep pauses the integration and emails the person who set it up, or the workspace owner. It pauses at once when retrying cannot help, for example when access was revoked or the destination was deleted. To turn it back on, fix the cause, open the integration and turn its switch back on. If it shows an error banner, click **Reactivate** instead. New submissions are sent again from then on. Use **Retry all** for the ones that failed in the meantime. ## Reconnecting an account Formstep keeps each connected account signed in on its own. When that stops working, for example because someone revoked Formstep’s access in the other tool, the integration shows **Connection expired**. Click **Reconnect** and sign in again. Every integration that uses that account starts working again. If the original account is not available, reconnect with another account that has access to the same destination. ## Recent events Each integration lists its last 20 events under **Recent events**: deliveries, tests and copies of past submissions. A failed event shows the destination’s own error message, which usually says what to fix. A failed copy of past submissions has a **Retry** button. > **Google Analytics and Axiom work differently** > > Analytics events are sent once and never retried. See [Analytics](https://formstep.io/docs/integrations/analytics-streaming/). ## FAQ **Will I lose submissions if an integration goes down?** No. Formstep saves every submission whatever the integration’s status. Send the failed ones again with **Retry all** once you fix the cause. **What if I reconnect with a different account?** The integration uses the new account. Make sure it can reach the same spreadsheet, database or channel, or change the integration’s setup. ## Next steps [Integrations overview](https://formstep.io/docs/integrations/overview/) — Every tool Formstep works with [Custom webhooks](https://formstep.io/docs/integrations/webhooks/) — Send each submission to your own URL --- ## Automation guides # Automation guides Step-by-step setup guides for the tools that drive Formstep: connect, pick a trigger or action, map fields, test. Start with the tool’s connect guide: every other guide for that tool assumes it is done. ## Zapier The [Formstep app for Zapier](https://zapier.com/apps/formstep/integrations) works on every Formstep plan. Zapier shows it as **Beta**, as it does every new app for its first months. You connect it from inside a Zap; **Open in Zapier** on the workspace Integrations page opens it with ready-made Zaps. - [Connect Formstep to Zapier](https://formstep.io/docs/guides/zapier/connect/) — Sign in to Formstep from a Zap, pick the workspace Zapier may use, and check that your forms show up. - [Send public link submissions to another app](https://formstep.io/docs/guides/zapier/public-link-submissions/) — Start a Zap when someone submits, edits or abandons a form through its share link, and send the answers to email, Sheets, Slack or a CRM. - [Send a request from a Zap](https://formstep.io/docs/guides/zapier/send-a-request/) — Create a Formstep request from any Zapier trigger, prefill and lock the answers you already know, send the link, then find, remind or cancel the request from a later Zap. - [Act on a request's outcome](https://formstep.io/docs/guides/zapier/request-outcome/) — Start a Zap when a request is completed, expires or is canceled, and branch on whether the recipient approved, declined or asked for changes. ## Make The [Formstep app for Make](https://www.make.com/en/hq/app-invitation/19b85d47851b8e2054970a52663f8290) is in beta: it is shared by invitation until Make lists it in its app directory. You connect it from inside a scenario. - [Connect Formstep to Make](https://formstep.io/docs/guides/make/connect/) — Install the Formstep app in your Make organization, sign in from a scenario, and pick the workspace Make may use. - [Start a scenario from public link submissions](https://formstep.io/docs/guides/make/public-link-submissions/) — Run a Make scenario when someone submits, edits or abandons a form through its share link, and map each answer by its question. - [Send requests and act on their outcome](https://formstep.io/docs/guides/make/requests/) — Create a Formstep request from a Make scenario, prefill it per field, and run a scenario when the recipient completes it, it expires or is canceled. ## n8n The Formstep community node runs on self-hosted n8n. It can also pause a workflow until the recipient answers. The last four guides are worked examples. - [Install the Formstep node in n8n](https://formstep.io/docs/guides/n8n/install-the-node/) — Give a self-hosted n8n a public HTTPS address, install the n8n-nodes-formstep community node, and check that its triggers and actions show up. - [Connect Formstep to n8n](https://formstep.io/docs/guides/n8n/connect/) — Connect n8n to one Formstep workspace with an API key, or on self-hosted n8n by signing in with OAuth2, and check that your forms show up. - [Start an n8n workflow from public link submissions](https://formstep.io/docs/guides/n8n/public-link-submissions/) — Run an n8n workflow when someone submits, edits or abandons a form through its share link, and pass the answers to the next node. - [Send a request and act on its outcome](https://formstep.io/docs/guides/n8n/send-a-request/) — Create a Formstep request from an n8n workflow, prefill and lock what you already know, then wait for the answer in the same workflow or start another one when a request ends. - [Look up, remind and cancel requests in n8n](https://formstep.io/docs/guides/n8n/manage-requests/) — Find a request by your own External ID, read its status and answers, send the recipient a reminder, or cancel it, all from an n8n workflow. - [Get a manager's approval in n8n](https://formstep.io/docs/guides/n8n/approval-from-a-manager/) — Example: send an expense claim to the manager who approves it, pause the workflow until they decide, then route each outcome and no answer. - [Read a PDF with AI, then get it confirmed](https://formstep.io/docs/guides/n8n/read-a-pdf-with-ai/) — Example: an AI model reads a supplier invoice in n8n, a request prefilled with what it read goes to the supplier, and they confirm or correct each value. - [Collect the documents each case needs in n8n](https://formstep.io/docs/guides/n8n/collect-documents/) — Example: build the form from an insurance claim's checklist inside the request, so each person is asked for exactly the documents their case needs, then save each upload with the claim. - [Let an AI agent send requests from n8n](https://formstep.io/docs/guides/n8n/ai-agent-tool/) — Example: an onboarding assistant in n8n that has the Formstep node as a tool, so it can send a client a prefilled form, check how the request is going, and remind them. ## AI agents Claude, ChatGPT, Cursor and other AI tools connect to Formstep over MCP, then build forms, send requests and read the answers. - [Connect an AI agent to Formstep](https://formstep.io/docs/guides/ai-agents/connect/) — Add Formstep to Claude, ChatGPT, Cursor or any tool that supports remote MCP servers, sign in, and pick the workspace the agent may use. - [Build a form with an AI agent](https://formstep.io/docs/guides/ai-agents/build-a-form/) — Ask Claude, ChatGPT, Cursor or another connected agent to build a Formstep form, watch it take shape in a preview, change it, and publish it. - [Send a request with an AI agent](https://formstep.io/docs/guides/ai-agents/send-a-request/) — Ask a connected agent to send a Formstep request to one person, prefill and lock what you already know, and read the answers back when they are done. ## Your own code Developers who call Formstep from their own code or an HTTP step: start at [Developers](https://formstep.io/docs/developers/overview/). # Connect Formstep to Zapier Sign in to Formstep from a Zap, pick the workspace Zapier may use, and check that your forms show up. ## Before you start - A Formstep account that is a member of the workspace you want to connect. - At least one published form in that workspace. Zapier lists unpublished forms too, marked _(not published)_, but only a published form sends events and can be requested. - A Zapier account. Any plan works for connecting. ## 1. Add Formstep to a Zap In Zapier, click **Create** › **Zaps**, then click the trigger or action step and search for **Formstep**. Pick any event for now; you can change it later. The list shows what the app can do: ![The Formstep app selected in a Zap step, with the Trigger event list open showing Public Link Submission Abandoned, Created and Updated](https://formstep.io/docs/guides/zapier/zapier-trigger-events.png) The Formstep trigger events. Request Completed, Expired and Canceled are further down the list. ## 2. Sign in to Formstep Under **Account**, click **Sign in** (or **Change** › **Connect a new account** if you already have one). Zapier opens a window; click **Connect**. ![Zapier's Connect to Zapier window for Formstep with a Connect button](https://formstep.io/docs/guides/zapier/zapier-connect-popup.png) If you are not signed in to Formstep in this browser, Formstep asks you to sign in first. Then it shows what Zapier may do and asks which workspace to connect. ## 3. Pick the workspace and authorize Choose the workspace under **Workspace** and click **Authorize**. The window closes and the account appears in the step, named after your Formstep email. ![Formstep's Authorize access screen: Zapier wants to connect to your Formstep workspace, with a list of permissions, a workspace picker set to Acme Inc, and Deny and Authorize buttons](https://formstep.io/docs/guides/zapier/formstep-authorize.png) The connection reaches only the workspace you pick here. > **One connection, one workspace** > > To use a second workspace, connect a new account and pick the other workspace on this screen. Zapier then shows both connections, and each Zap step uses the one you choose. ![The Account field in a Zap step showing the connected Formstep account, with the account picker open listing admin@formstep.io and a Connect a new account button](https://formstep.io/docs/guides/zapier/zapier-account-connect.png) ## 4. Check the connection Pick an event and continue to **Configure**. The **Form** dropdown lists the forms of the connected workspace: ![The Configure step with the Form dropdown open, listing the Approval form and its id](https://formstep.io/docs/guides/zapier/zapier-choose-form.png) If a form is missing, click Refresh. Forms from other workspaces never show here. ## Disconnect In Zapier, open **App connections**, find Formstep, and remove the connection. Zaps that use it stop until you connect Formstep again and pick the new connection in each of them. Removing it in Zapier does not end Zapier’s access on the Formstep side. In Formstep, open **OAuth and API Keys** and click **Disconnect** next to Zapier under **Connected apps** as well. ## Next steps [Send public link submissions to another app](https://formstep.io/docs/guides/zapier/public-link-submissions/) — Start a Zap when someone submits, edits or abandons a form [Send a request from a Zap](https://formstep.io/docs/guides/zapier/send-a-request/) — Create a request from any trigger, prefilled and locked # Send public link submissions to another app Start a Zap when someone submits, edits or abandons a form through its share link, and send the answers to email, Sheets, Slack or a CRM. ## Before you start You need a [Formstep connection in Zapier](https://formstep.io/docs/guides/zapier/connect/) and a published form with a share link. A submission to a request link does not start these Zaps; see [Act on a request’s outcome](https://formstep.io/docs/guides/zapier/request-outcome/) for that. ## 1. Pick the trigger Create a Zap, choose **Formstep** as the trigger app, and pick one of the three public link events: | Trigger | Fires when | Needs | | - | - | - | | Public Link Submission Created | A respondent submits the form through a share link. | Nothing extra. | | Public Link Submission Updated | A respondent edits a submission they already sent. | [Edit after submit](https://formstep.io/docs/submissions-analytics/edit-after-submit/) turned on for the form. | | Public Link Submission Abandoned | A respondent starts the form and leaves it unfinished for the time you choose, from 12 hours to 1 week. | A Pro or Business plan, which saves [partial submissions](https://formstep.io/docs/submissions-analytics/partial-submissions/#unfinished-drafts). | ![The Formstep trigger event list: Public Link Submission Abandoned, Created and Updated, each marked Instant](https://formstep.io/docs/guides/zapier/zapier-trigger-events.png) ## 2. Choose the form Pick your account, click **Continue**, and choose the form under **Form**. For the Abandoned trigger, also set **Consider submission abandoned after**. Formstep checks once an hour, so the Zap can start up to an hour after that time. ![The Configure step with the Form dropdown open, listing the Approval form](https://formstep.io/docs/guides/zapier/zapier-choose-form.png) ## 3. Test the trigger Click **Continue**, then **Test trigger**. Zapier loads a record such as **Public Link Submission A**. Open it to see the fields you can map: ![The test record for Public Link Submission Created, with data › answers listing Your Decision (decision), Signature, What should change? and Your Name, and data › display listing the same questions](https://formstep.io/docs/guides/zapier/zapier-submission-sample.png) The test record is a sample built from the form's questions (John Doe), not your latest submission. > **The test record is a sample** > > It carries every question of the form with made-up answers, so you can map fields before anyone has submitted. Real runs carry the respondent’s answers. The sample has **Test Event** set to `true`; real submissions have `false`. Every answer appears twice: - **answers** holds the value a workflow compares: the [option key](https://formstep.io/docs/requests/field-keys/) of a choice, like `approve`, a number, a date. - **display** holds the text a person reads: the option’s label (`Approve`), a formatted date. Where a field key reads differently from its question title, the label names the key in parentheses: **Your Decision (decision)** is the question whose [field key](https://formstep.io/docs/requests/field-keys/) is `decision`. The readable text of the same answer is **Your Decision (decision, display)**. A mapped field shows the same label. Use **display** in messages and **answers** in filters and lookups. The full shape of both is in the [webhooks reference](https://formstep.io/docs/developers/webhooks-reference/). ## 4. Add the action and map the fields Click **Continue with selected record** and add the action. The example uses **Email by Zapier** › **Send Outbound Email**. In each field, type `/` or click **+** and search for the question by its title: ![The Insert data panel searching for Your Name, showing it under Data › Answers and under Data › Display](https://formstep.io/docs/guides/zapier/zapier-data-picker.png) ![The Send Outbound Email step: To team@acme.example, Subject 'New approval from' followed by Your Name (display), Body 'Decision:' followed by Your Decision (decision, display)](https://formstep.io/docs/guides/zapier/zapier-map-fields.png) Subject and body read from display, so the email shows labels, not option keys. ## 5. Publish and check a real run Click **Publish**. Open the form’s share link, submit it, and open **Zap history** in Zapier. The run appears within seconds: ![Zap history showing a Successful run of the Approval submission to email Zap](https://formstep.io/docs/guides/zapier/zapier-zap-history.png) Open the run to see the real answers the Zap received. ## If the Zap does not start - A submission through a request link fires Request Completed, not Public Link Submission Created. - A Zap that is off never runs. Check the switch next to the Zap’s name. - The trigger listens to the form in the workspace its connection points at. Check that the Zap uses the right connection. ## Next steps [Send a request from a Zap](https://formstep.io/docs/guides/zapier/send-a-request/) — Create a request from any trigger, prefilled and locked [Integrations overview](https://formstep.io/docs/integrations/overview/) — Slack, Sheets and Notion without Zapier # Send a request from a Zap Create a Formstep request from any Zapier trigger, prefill and lock the answers you already know, send the link, then find, remind or cancel the request from a later Zap. ## Before you start You need a [Formstep connection in Zapier](https://formstep.io/docs/guides/zapier/connect/) and a published form. What a request is, and what it can carry, is in [Creating requests](https://formstep.io/docs/requests/creating-requests/). The steps here show where each option sits in Zapier. For an approval, open **Templates** in Formstep and pick **Approval**. It holds a [Decision question](https://formstep.io/docs/requests/decisions-and-approvals/) with Approve, Decline and Request changes, a text box for the changes, and a signature for approval. For what the app is good for, and how it replaces Approval by Zapier, see [Formstep for Zapier](https://formstep.io/integrations/zapier/). ## 1. Add the Create Request step Start with the trigger that should send the request, such as a deal marked won in your CRM or a new row in Google Sheets. Add an action, choose **Formstep**, and pick **Create Request**. ## 2. Pick the form, the recipient and the delivery - **Form** is the form the recipient completes. It must be published. - **Recipient Email** and **Recipient Name** usually come from the trigger. The email is needed for email delivery and for reminders. - **Language** is one of the form’s published languages, like `de`. Leave it empty for the form’s default. - **Delivery** decides who sends the link. **Email** lets Formstep send the [invitation and reminders](https://formstep.io/docs/requests/invitations-and-reminders/), on Pro and Business. **None**, the default, sends no invitation: a later step of your Zap sends the **Request URL** by Slack, SMS or your own email tool. That makes the Zap three steps, which needs a paid Zapier plan. ![The Create Request step: Form Approval, Recipient Email ada@acme.example, Recipient Name Ada Lovelace, Language empty, Delivery 'None: the Zap sends the link itself'](https://formstep.io/docs/guides/zapier/zapier-create-request-fields.png) With a Recipient Email, Formstep still emails the form’s scheduled reminders even when Delivery is None. To send none, leave Recipient Email empty. ## 3. Prefill and lock what you already know Once you pick the form, each question you can prefill appears below as a field. Fill in any answer you already have. Add a question to **Read-only fields** to lock it. Fields marked **(context)** are the form’s hidden fields, and a **Documents** field takes files from earlier steps when the form has a Documents block. What each of these does is in [Prefill, locked fields and hidden fields](https://formstep.io/docs/requests/creating-requests/#three-buckets). ![Test Request set to False, Your Name prefilled with Ada Lovelace, Your Decision and What should change? left empty, and Your Name picked under Read-only fields](https://formstep.io/docs/guides/zapier/zapier-create-request-prefill.png) The recipient opens the link and finds their name filled in and locked: ![The request form with Your Name showing Ada Lovelace in a greyed-out field and the note 'Filled in by the sender.'](https://formstep.io/docs/guides/zapier/formstep-prefill-locked.png) ## 4. Set an External ID Put your own id for this piece of work in **External ID**, like the deal number `deal-1042`. A later Zap can then [find the request](#find-a-request) by it. If Zapier runs the step again with the same External ID and the same inputs, you get the same request back instead of a second one. Use a new External ID for each request, and a fresh one when you change a test step’s inputs. **Reminders**, **Expires At** and **Metadata** are optional. For expiry and reminder defaults, see [Invitations, reminders and expiry](https://formstep.io/docs/requests/invitations-and-reminders/). ## 5. Test the step Click **Test step**. Formstep creates the request and returns it. **Request URL** is the link for the recipient; map it into the step that sends it. ![The Data out tab of the Create Request test: Created At and Expires At as dates, Deduplicated false, Delivery Status not\_requested, External ID deal-1043, Status pending and a Request URL on form.formstep.io](https://formstep.io/docs/guides/zapier/zapier-create-request-output.png) Delivery Status is not\_requested because Delivery is None: Formstep sent nothing. > **Testing the step creates a real request** > > Unless **Test Request** is on, the test is a real request: with Email delivery, the recipient gets the invitation. Turn Test Request on while you build the Zap. A test request sends no email, counts nowhere, and does not start any Zap with a [request trigger](https://formstep.io/docs/guides/zapier/request-outcome/). Turn it off before you publish. To test the whole path, turn Test Request off, set **Delivery** to **None**, use your own email and open the **Request URL** yourself. ## Find, remind and cancel requests Four more Formstep actions work on a request that already exists. A typical Zap finds the request by the id your system knows it by, then reminds or cancels it. The same actions are on [the Requests page](https://formstep.io/docs/requests/managing-requests/). ### Find a request by External ID Your CRM knows the deal as `deal-1042`, not by Formstep’s request ID. Add a Formstep action with the event **Find Request** and map your id into **External ID**: ![The Find Request step: External ID deal-1042, Form empty, Include Test Requests empty, and Zapier's options for no results and multiple results](https://formstep.io/docs/guides/zapier/zapier-find-request-fields.png) - **Form** narrows the search to one form. Leave it empty to search the whole workspace. - **Include Test Requests** is off by default, so a test request is not found. Turn it on while you build the Zap. - If several requests share the External ID, the newest comes first, and Zapier uses it unless you change **If multiple search results are found**. The result is the whole request: its status, outcome, recipient, dates, and what you prefilled and locked. ![The Find Request test result: Test Request false, Language en, Last Activity At and Opened At as dates, Outcome approve, prefill with Your Name Ada Lovelace, and Readonly Keys your\_name](https://formstep.io/docs/guides/zapier/zapier-find-request-output.png) ### Read the answers with Get Request **Get Request** takes a **Request**: map the Request ID from a Create Request step, a Find Request step or a request trigger. It returns the request, its link, and the answers once it is completed. Pick the request’s form under **Form** so the answers are labelled with the question titles. ### Send a reminder with Remind Request **Remind Request** emails the recipient a reminder now, on Pro and Business. Scheduled reminders still go out as planned. The request must be pending and have a recipient email, and a test request is never reminded. How often you can remind someone is in [Invitations, reminders and expiry](https://formstep.io/docs/requests/invitations-and-reminders/). ### Withdraw a request with Cancel Request **Cancel Request** withdraws a pending request. The link stops working, and the recipient sees a withdrawn notice instead of the form. Fill in **Reason** to record why: the [Request Canceled](https://formstep.io/docs/guides/zapier/request-outcome/) trigger carries it, so another Zap can tell your team. A request that is already completed, expired or canceled stays as it is, and the step fails. ## Next steps [Act on a request's outcome](https://formstep.io/docs/guides/zapier/request-outcome/) — Start a Zap when the recipient approves, declines or asks for changes [Decisions & approvals](https://formstep.io/docs/requests/decisions-and-approvals/) — The Decision question and its three values [Zapier templates](https://formstep.io/workflows/) — Three Zaps spelled out step by step: CRM prefill, PDF reading with AI, and onboarding [Form templates](https://formstep.io/templates/) — Forms to start from, each with its field keys listed # Act on a request's outcome Start a Zap when a request is completed, expires or is canceled, and branch on whether the recipient approved, declined or asked for changes. ## Before you start You need a [Formstep connection in Zapier](https://formstep.io/docs/guides/zapier/connect/) and requests to act on, sent from a Zap as in [Send a request from a Zap](https://formstep.io/docs/guides/zapier/send-a-request/) or from anywhere else: the dashboard, the API or an agent. To branch on a verdict, the form needs a [Decision question](https://formstep.io/docs/requests/decisions-and-approvals/). ## 1. Pick the trigger Create a Zap, choose **Formstep** as the trigger app, and search for **Request**: ![The Formstep trigger list filtered by Request: Request Canceled, Request Completed and Request Expired, each marked Instant](https://formstep.io/docs/guides/zapier/zapier-request-trigger-events.png) | Trigger | Fires when | Carries | | - | - | - | | Request Completed | The recipient submits the form. | The request, the outcome and every answer. | | Request Expired | The request reaches its expiry date unanswered. | The request only. | | Request Canceled | Someone cancels the request, from the dashboard, the API or a Cancel Request step, or Formstep cancels it because the form was moved to trash. | The request and the cancel reason. | Pick **Request Completed**, pick your account, and choose the form. The Zap fires for requests to that form only. ## 2. Test the trigger Click **Test trigger**. As with public link triggers, Zapier loads a sample built from the form’s questions, so you can map fields before any request is completed: ![The Request B test record: data › answers with Your Decision (decision) approve, data › display with Your Decision (decision, display) Approve, then data › form and data › request](https://formstep.io/docs/guides/zapier/zapier-request-sample.png) Where a field key reads differently from its question title, the label names the key in parentheses. **Your Decision (decision)** is the answer whose field key is `decision`, and a mapped field shows the same label. Besides **answers** and **display**, a request event carries a **request** group: - **Recipient Email**, **Recipient Name**, **Request External ID**, **Metadata** and **Context**: what you set when you created the request. Use the External ID to find the record the request was about. - **Outcome**: `approve`, `decline` or `changes`, the recipient’s answer to the Decision question. On this form it is the same value as **Your Decision (decision)**, because the Decision question’s key is `decision`. Outcome is missing when the form has no Decision question. Zapier receives the same event as a callback. Developers: every field is in the [callback reference](https://formstep.io/docs/developers/callbacks/#payload). ## 3. Map the action Add the action. The example uses **Email by Zapier** › **Send Outbound Email**, with the recipient’s name and their decision in the subject and the External ID in the body: ![The Send Outbound Email step: To is team@acme.example; Subject is Recipient Name, the word answered, then Your Decision (decision, display); Body is External ID followed by Request External ID](https://formstep.io/docs/guides/zapier/zapier-outcome-email.png) Publish the Zap. When the recipient completes the request, the email arrives with a subject like _Ada Lovelace answered: Approve_, and the run appears in **Zap history**. ## 4. Continue only on approval To act on one verdict only, add a **Filter** step between the trigger and the action. Set it to continue only if **Your Decision (decision)** exactly matches `approve`: ![A Filter step: Only continue if 1. Your Decision (decision) (Text) Exactly matches approve](https://formstep.io/docs/guides/zapier/zapier-filter-approve.png) The filter reads the field key, not the label. The Decision question’s field key is always `decision`, so the same filter works on every form, whatever the question’s title. **Outcome** holds the same value. Compare against the value from **answers**. The label from **display** changes when you rename the option or the recipient answers in another language, while the value stays the same. To handle each verdict differently, use **Paths** instead of a filter, one path per value. For example, `approve` moves the deal on, `decline` tells the account owner, and `changes` sends the answer to **What should change?** to the account owner. > **Filters and Paths need a paid Zapier plan** > > A Zap with a Filter or Paths step has more than two steps, and Zapier’s Free plan publishes only two-step Zaps. On Free, keep the trigger and one action, and branch in the app the action writes to. ## If the Zap does not start - A test request never starts a Zap, whether it was created with Test Request on or with **Try it yourself**. Send a real request to check the Zap. - The trigger listens only to the form you chose in step 1. - The Zap is off, or uses a connection to another workspace. See the checks in [the public link guide](https://formstep.io/docs/guides/zapier/public-link-submissions/#troubleshooting). ## Next steps [Find, remind and cancel requests](https://formstep.io/docs/guides/zapier/send-a-request/#manage-requests) — Find a request by External ID, remind the recipient, or withdraw it [Decisions & approvals](https://formstep.io/docs/requests/decisions-and-approvals/) — The Decision question and its three values # Connect Formstep to Make Install the Formstep app in your Make organization, sign in from a scenario, and pick the workspace Make may use. ## Before you start - A Formstep account that is a member of the workspace you want to connect. - At least one published form in that workspace. - A Make account. Any plan works. > **Beta** > > The Formstep app for Make is shared by invitation until Make approves it for its app directory. When you connect it, you tell it which Make zone your organization is in. ## 1. Install the app Open the [Formstep invitation in Make](https://www.make.com/en/hq/app-invitation/19b85d47851b8e2054970a52663f8290) and install the app in your organization. You can also open it from Formstep: **Integrations** › **Make** › **Open in Make**. If you skip this step, Make asks for it the first time you run a scenario with a Formstep module: confirm the installation there. ## 2. Add Formstep to a scenario Create a scenario, click the big **+**, and search for **Formstep**. The app lists its modules: - **Watch public link submissions** and **Watch requests**: triggers that start the scenario the moment something happens in Formstep. - **Create a request**, **Get a request**, **Cancel a request**, **Remind a request** and **Search requests**: actions on your requests. - **Make an API call**: any other method of the [Formstep API](https://formstep.io/docs/developers/rest-api/). ## 3. Sign in and pick the workspace Pick any module. Next to **Connection**, click **Create a connection** (in a trigger, first click **Create a webhook**) and give it a name. Under **Make zone**, pick the zone of your Make organization: the start of the address you use Make at, such as `eu1` in `eu1.make.com` or `us2` in `us2.make.com`. Click **Save**. Make opens a window. If you are not signed in to Formstep in this browser, Formstep asks you to sign in first. Then it shows what Make may do and asks which workspace to connect. Choose the workspace and click **Authorize**. The window closes and the connection appears in the module. In **Credentials**, Make shows the workspace name under the connection. > **The window ends on "Resource not found"** > > The **Make zone** does not match your organization. Close the window, create the connection again and pick the zone in your Make address. > **One connection, one workspace** > > To use a second workspace, add another connection and pick the other workspace on Formstep’s screen. Each module then uses the connection you choose. ## 4. Check the connection Open the **Form** dropdown of the module. It lists the forms of the connected workspace. Forms from other workspaces never show here. If a new form is missing, click **Refresh** next to the field. ## Disconnect In Make, open **Credentials**, find the Formstep connection and delete it. Scenarios that use it stop until you add a new connection and pick it in each module. Deleting it in Make does not end Make’s access on the Formstep side. In Formstep, open **OAuth and API Keys** and click **Disconnect** next to Make under **Connected apps** as well. ## Next steps [Start a scenario from public link submissions](https://formstep.io/docs/guides/make/public-link-submissions/) — Run a scenario when someone submits, edits or abandons a form [Send requests and act on their outcome](https://formstep.io/docs/guides/make/requests/) — Create a request, then run a scenario when it ends # Start a scenario from public link submissions Run a Make scenario when someone submits, edits or abandons a form through its share link, and map each answer by its question. ## Before you start You need a [Formstep connection in Make](https://formstep.io/docs/guides/make/connect/) and a published form with a share link. A submission to a request link does not start this trigger; [Watch requests](https://formstep.io/docs/guides/make/requests/#watch-requests) covers those. ## 1. Add the trigger Create a scenario, click the big **+**, search for **Formstep** and pick **Watch public link submissions**. Under **Webhook**, click **Create a webhook**, pick your connection, the **Form**, and the **Event**: | Event | Fires when | Needs | | - | - | - | | Submission created | A respondent submits the form through a share link. | Nothing extra. | | Submission updated | A respondent edits a submission they already sent. | [Edit after submit](https://formstep.io/docs/submissions-analytics/edit-after-submit/) turned on for the form. | | Submission abandoned | A respondent starts the form and leaves it unfinished for the time you choose, from 12 hours to 1 week. | A Pro or Business plan, which saves [partial submissions](https://formstep.io/docs/submissions-analytics/partial-submissions/#unfinished-drafts). | Click **Save**. Formstep now sends that event for that form to Make. For the abandoned event, Formstep checks once an hour, so the scenario can start up to an hour after the time you chose. > **One trigger, one event** > > Each trigger hears one event. To act on new submissions and on edits, add two scenarios, one per event. A submission through a request link never reaches this trigger, so a scenario that watches both requests and public links on the same form runs once per completion. ## 2. Test the trigger Click **Run once**, then submit the form through its share link. The trigger shows one bundle. Open it to see what arrives: | Field | Holds | | - | - | | Event type | `submission.completed`, `submission.updated` or `submission.abandoned`. | | Data › Answers | The value a scenario compares: the [option key](https://formstep.io/docs/requests/field-keys/) of a choice, a number, a date. | | Data › Answers (display) | The text a person reads: the option label, a formatted date. | | Data › Form | The form the submission belongs to. | | Data › Submission | The submission's id, time, language and PDF link. | ## 3. Map the answers Add the next module. In its fields, open the mapping panel: each question of the form is listed under **Answers** and **Answers (display)** by its title. Use **Answers (display)** in messages and **Answers** in filters and lookups. The list comes from the form’s published questions. If you add a question, publish the form and reopen the module so Make loads it. The full shape of the event is in the [webhooks reference](https://formstep.io/docs/developers/webhooks-reference/). ## 4. Turn the scenario on Save the scenario and switch it on. Formstep sends every new event as it happens. Deleting the trigger’s webhook in Make removes the subscription in Formstep too. ## Next steps [Send requests and act on their outcome](https://formstep.io/docs/guides/make/requests/) — Create a request, then run a scenario when it ends [Webhook API reference](https://formstep.io/docs/developers/webhooks-reference/) — Format, signing and retries, for developers # Send requests and act on their outcome Create a Formstep request from a Make scenario, prefill it per field, and run a scenario when the recipient completes it, it expires or is canceled. ![A Make scenario with three Formstep modules: Create a request, Get a request and Make an API call, each with a green check after a run](https://formstep.io/docs/guides/make/make-request-scenario.png) ## Before you start You need a [Formstep connection in Make](https://formstep.io/docs/guides/make/connect/) and a published form. A request is one recipient’s copy of the form; see [Requests](https://formstep.io/docs/requests/overview/) for how they work. ## 1. Create a request Add **Formstep** › **Create a request** and pick the **Form**. Picking it loads one input per question under **Prefill**, one per hidden field under **Context**, and the **Read-only fields** picker: ![The Create a request module with the Make smoke test form picked, a Prefill input for Full name and one for Company set to Prefilled Co, and the Read-only fields picker](https://formstep.io/docs/guides/make/make-create-request-prefill.png) A choice question takes its option key, a date takes YYYY-MM-DD. | Field | What it does | | - | - | | Recipient email and name | Who the request is for. An email is needed to send the invitation and reminders. | | Delivery | **I deliver the link myself**: the scenario sends the request link, for example by email or SMS. **Email the invitation**: Formstep emails it to the recipient. | | External ID | Your own id, such as a ticket or order number. Running the module again with the same external ID and inputs returns the same request instead of a second one. | | Test request | A dry run: nothing is emailed, the link opens and can be completed, and it counts nowhere. Test requests never reach a trigger. | The module returns the request: **Request ID**, **Request link** and **Status** ( `pending`). Map the link into your email or message module when you deliver it yourself. ## 2. Act when the recipient answers In a second scenario, add **Formstep** › **Watch requests**. Under **Webhook**, click **Create a webhook**, pick the **Form** and the **Event**: | Event | Fires when | Carries | | - | - | - | | Request completed | The recipient submits the request. | The request, the answers and the submission. | | Request expired | The request reaches its expiry date unanswered. | The request. | | Request canceled | Someone cancels the request. | The request, with the reason. | ![A Watch requests bundle for request.completed: Data › Answers lists Company and Full name, Answers (display) lists the same questions, then Form, Request and Submission](https://formstep.io/docs/guides/make/make-watch-requests-bundle.png) Answers and Answers (display) list each question by its title, the same as in Watch public link submissions. **Data › Request** holds the request’s id, your external ID, its status, the recipient and, with a [Decision question](https://formstep.io/docs/requests/decisions-and-approvals/), the **outcome**: `approve`, `decline` or `changes`. Add a filter on it to branch on the verdict. Test requests never reach this trigger. ## 3. Look up and manage requests | Module | Use it to | | - | - | | Get a request | Read one request by its id: status, outcome, timeline, and the answers once it is completed. Pick the form so the answers map by question. | | Search requests | List a form's requests, newest first, by status or external ID. | | Cancel a request | Withdraw a pending request, with an optional reason. Its link stops working. | | Remind a request | Email the recipient a reminder now. Needs a recipient email and Pro or Business; see [Invitations, reminders and expiry](https://formstep.io/docs/requests/invitations-and-reminders/). | | Make an API call | Call any other method of the [Formstep API](https://formstep.io/docs/developers/rest-api/) with the same connection. | > **When a module fails** > > The error message says why in words, for example that the request is already canceled. Reminding too soon after the last reminder, or canceling a request that is no longer pending, fails the same way. Add an error handler to the module to route these cases. ## Next steps [Field keys](https://formstep.io/docs/requests/field-keys/) — The short names your scenarios prefill and read answers by [Invitations, reminders & expiry](https://formstep.io/docs/requests/invitations-and-reminders/) — Email the link, chase people who go quiet, close stale requests # Install the Formstep node in n8n Give a self-hosted n8n a public HTTPS address, install the n8n-nodes-formstep community node, and check that its triggers and actions show up. The setup guides were checked on self-hosted n8n 2.40 in Docker with version 0.11.1 of the node, and the examples on n8n 2.44 with version 0.13.0. ## Before you start - n8n 2.30 or later. The Formstep credential needs it. - A Formstep account. Everything in these guides works on the Free plan except email invitations, reminders and unfinished drafts, which need Pro or Business. See [Limits and quotas](https://formstep.io/docs/subscription-billing/limits-quotas/). ## Self-hosted n8n A self-hosted n8n needs two things before it can install the node: - The owner account or an admin account. Only the owner and admins can install community nodes. - A public HTTPS address, from a reverse proxy or a tunnel. Formstep sends events and request callbacks only to `https://` URLs on the public internet. An n8n that is reachable only on `localhost` can connect but never receives anything. ### 1. Tell n8n its public address n8n builds the URLs it hands to Formstep from its own settings, not from the address in your browser. Set these environment variables on the n8n container or process: - `WEBHOOK_URL`: the public HTTPS address, with a trailing slash, like `https://n8n.example.com/`. n8n builds the URL of a published trigger and the resume URL of [Wait for the Outcome](https://formstep.io/docs/guides/n8n/send-a-request/#wait-for-the-outcome) from it. - `N8N_EDITOR_BASE_URL`: the same address. n8n builds a trigger’s test URL for **Execute step** from it, and the **OAuth Redirect URL** of the Formstep credential. - `N8N_PROXY_HOPS`: set it to `1` when n8n runs behind one reverse proxy or tunnel, so n8n reads the proxy’s forwarded headers. With Docker, that looks like this: ```bash docker run -d --name n8n -p 5678:5678 \ -e WEBHOOK_URL=https://n8n.example.com/ \ -e N8N_EDITOR_BASE_URL=https://n8n.example.com/ \ -e N8N_PROXY_HOPS=1 \ -v n8n_data:/home/node/.n8n \ docker.n8n.io/n8nio/n8n ``` With docker compose or an npm install, set the same variables in n8n’s environment. Point your proxy or tunnel at port `5678`, pass `/webhook/`, `/webhook-test/` and `/webhook-waiting/` through unchanged, and open n8n through the public address from then on. > **Formstep sends events to HTTPS addresses only** > > Formstep refuses to send events to an `http://` URL or a private network address, and the Formstep nodes say so before they try, with the URL n8n handed out. If a trigger shows this on **Execute step**, `N8N_EDITOR_BASE_URL` is not set; on **Publish**, or on a Create request node with **Wait for the Outcome**, `WEBHOOK_URL` is not. Set that variable, restart n8n, and try again. If you change `WEBHOOK_URL` later, unpublish and publish each Formstep workflow again so its trigger registers the new address. With version 0.12 of the node or older, the same problem reads _VALIDATION\_ERROR: targetUrl must use https_. ### 2. Install the community node Open **Settings** › **Community nodes** and click **Install**. Type `n8n-nodes-formstep` under **npm Package Name**, tick the box that says you understand the risks of installing unverified code, and click **Install**. ![The Install community nodes dialog: npm Package Name n8n-nodes-formstep, the risk checkbox ticked, and an Install button](https://formstep.io/docs/guides/n8n/n8n-install-community-node.png) After a few seconds the package appears in the list with its two nodes: ![Settings › Community nodes listing n8n-nodes-formstep, version 0.11.1, with 2 nodes: Formstep, Formstep Trigger](https://formstep.io/docs/guides/n8n/n8n-community-node-installed.png) Update or uninstall the package from the same list. If **Community nodes** is missing from Settings, your n8n was started with `N8N_COMMUNITY_PACKAGES_ENABLED=false`; remove that setting. ## n8n Cloud > **Not available on n8n Cloud yet** > > n8n Cloud installs only community nodes that n8n has verified, and the Formstep node is not verified yet. Until it is, use a self-hosted n8n, or call the [REST API](https://formstep.io/docs/developers/quickstart-send-a-request/) from an **HTTP Request** node. A Cloud instance already has a public HTTPS address, so once the node is available there, only the install steps change. ## Check the node Open a workflow, click **+** and search for **Formstep**. The node details show six actions and six triggers: ![The node details of Formstep, marked Installed, with Actions (6): Cancel request, Create request, Get request, Get many requests, Remind request recipient and Replay request callback, and a collapsed Triggers (6) section](https://formstep.io/docs/guides/n8n/n8n-node-details.png) - **Formstep Trigger** starts a workflow on a public link submission that is created, updated or abandoned, or on a request that is completed, expires or is canceled. - **Formstep** creates a request, gets one or many, reminds the recipient, cancels a request, and replays its callback. ## Next steps [Connect Formstep to n8n](https://formstep.io/docs/guides/n8n/connect/) — Create the credential, sign in, and pick the workspace [Automation guides](https://formstep.io/docs/guides/overview/) — Every guide for Zapier, Make, n8n and AI agents # Connect Formstep to n8n Connect n8n to one Formstep workspace with an API key, or on self-hosted n8n by signing in with OAuth2, and check that your forms show up. ## Before you start - The Formstep node installed on your n8n. See [Install the Formstep node in n8n](https://formstep.io/docs/guides/n8n/install-the-node/), which also covers what your n8n needs first. - A Formstep account that is a member of the workspace you want to connect. - At least one published form in that workspace. ## Choose how to connect Each Formstep node has an **Authentication** choice: - **API Key**, the default, works on n8n Cloud and on self-hosted n8n. You copy a key from Formstep and paste it into n8n. - **OAuth2** works on self-hosted n8n only. You sign in to Formstep from n8n instead of copying a key. Nodes added before version 0.14.0 of the node use it, and keep using it. ## Connect with an API key 1. In Formstep, open **OAuth and API Keys** in the workspace n8n should use. Click **Create token** and name it after your n8n, for example _n8n_. Under **Expiration**, pick **No expiration**, then click **Create token**. 2. Copy the key. Formstep shows it only once; it starts with `fs_`. 3. In n8n, add a Formstep node to a workflow, open **Credential** and click **Create new credential**. n8n opens a **Formstep API** credential. 4. Paste the key into **API Key** and click **Save**. n8n checks the key with Formstep and saves the credential. > **Pick No expiration** > > A key created with 30 or 90 days stops working on that date, and every workflow that uses it stops with an `UNAUTHORIZED` error. With No expiration, the key works until you delete it. To replace a key, create a new one, paste it into the same credential in n8n and save. ## Connect with OAuth2 (self-hosted n8n) In the Formstep node, set **Authentication** to **OAuth2**, open **Credential** and click **Create new credential**. You can also start from **Overview** › **Credentials** › **Create** and search for **Formstep OAuth2 API**. ![The Formstep OAuth2 API credential: a Connect button, the OAuth Redirect URL https://n8n.example.com/rest/oauth2-credential/callback, and Allowed HTTP Request Domains set to All](https://formstep.io/docs/guides/n8n/n8n-credential-connect.png) There is nothing to fill in. n8n registers itself with Formstep when you click **Connect**, including the **OAuth Redirect URL** shown here. The note below that URL asks you to enter it in Formstep; you can ignore it, because Formstep has no place to paste it. Click **Connect**. n8n opens a Formstep window. If you are not signed in to Formstep in this browser, sign in first. Formstep then shows what n8n may do and asks which workspace to connect: ![Formstep's Authorize access screen: n8n wants to connect to your Formstep workspace from n8n.example.com, with a list of permissions, a workspace picker set to Acme Inc, and Deny and Authorize buttons](https://formstep.io/docs/guides/n8n/formstep-authorize.png) The connection reaches only the workspace you pick here. Choose the workspace under **Workspace** and click **Authorize**. The window reports that the connection succeeded, and the credential in n8n shows as connected. Save it. The connection refreshes itself while a workflow uses it at least once every 30 days. > **One credential, one workspace** > > To use a second workspace, create a second credential: a key from that workspace, or another OAuth2 sign-in with the other workspace picked. Rename each credential after the workspace it reaches. Each Formstep node then uses the credential you choose in it. ## Check the connection Add a **Formstep Trigger** and pick the credential. **Form** lists the forms of the connected workspace, and you can search it by name; to paste a form’s ID instead, switch to **By ID**. A form marked _(not published)_ sends no events and takes no requests until you publish it. ![The Formstep Trigger with Credential Formstep account and Form set to From list, open on a search box above \[Guide\] Supplier onboarding and Approval](https://formstep.io/docs/guides/n8n/n8n-choose-form.png) Forms from other workspaces never show here. A trigger saved before version 0.15.0 keeps a plain dropdown, Form Name or ID. ## Disconnect In n8n, delete the credential under **Overview** › **Credentials**. Workflows that use it stop until you pick another credential in each Formstep node. Deleting it in n8n does not end n8n’s access on the Formstep side. In Formstep, open **OAuth and API Keys**. For an API key, click the bin icon next to the key under **API Keys**. For OAuth2, click the bin icon next to n8n under **Connected apps** and confirm with **Disconnect**. ## Next steps [Start an n8n workflow from public link submissions](https://formstep.io/docs/guides/n8n/public-link-submissions/) — Run a workflow when someone submits, edits or abandons a form [Send a request and act on its outcome](https://formstep.io/docs/guides/n8n/send-a-request/) — Create a request, then branch on what the recipient decided # Start an n8n workflow from public link submissions Run an n8n workflow when someone submits, edits or abandons a form through its share link, and pass the answers to the next node. ## Before you start You need a [Formstep credential in n8n](https://formstep.io/docs/guides/n8n/connect/) and a published form with a [share link](https://formstep.io/docs/sharing-publishing/sharing-embedding/#share-links). Your n8n must be reachable from the internet over HTTPS; see [Install the Formstep node in n8n](https://formstep.io/docs/guides/n8n/install-the-node/). A submission to a request link does not start these workflows; see [Act on the outcome](https://formstep.io/docs/guides/n8n/send-a-request/#outcome) for that. ## 1. Add the trigger Create a workflow, click **+**, search for **Formstep** and pick **Formstep Trigger**. Choose the credential, the form, and one of the three public link events under **Event**: ![The Formstep Trigger's Event dropdown open: Public Link Submission Created, Updated and Abandoned, then Request Completed, Expired and Canceled, each with a short description](https://formstep.io/docs/guides/n8n/n8n-trigger-events.png) | Event | Runs when | Needs | | - | - | - | | Public Link Submission Created | A respondent submits the form through a share link. | Nothing extra. | | Public Link Submission Updated | A respondent edits a submission they already sent. | [Edit after submit](https://formstep.io/docs/submissions-analytics/edit-after-submit/) turned on for the form. | | Public Link Submission Abandoned | A respondent leaves the form unfinished for the time under **Consider Abandoned After** (12 hours to 1 week). Formstep checks hourly, so the run can come up to an hour later. | A Pro or Business plan, which saves [partial submissions](https://formstep.io/docs/submissions-analytics/partial-submissions/#unfinished-drafts). | ## 2. Listen for a test event Click **Execute step**. The node shows _Listening for test event_, and Formstep starts sending this form’s events to the node’s test URL for two minutes. Open the form’s share link in another tab and submit it within that time. The submission arrives in the node within seconds: ![The Formstep Trigger after a test event: Form \[Guide\] Supplier onboarding, Event Public Link Submission Created, and the output with id, type submission.completed, test false and data › answers holding company\_name Nordlys Tekstil AS, contact\_email, vat\_number, the payment terms answer yes, and the uploaded certificate file](https://formstep.io/docs/guides/n8n/n8n-submission-output.png) n8n has no sample record: the test is a real submission, and `test` is `false`. Every answer appears twice: - **data.answers** holds the value a workflow compares, under the question’s [field key](https://formstep.io/docs/requests/field-keys/): an option key like `yes`, a number, a date, or a list of files. - **data.display** holds the text a person reads, like `Yes`. Use **display** in messages and **answers** in conditions. See the [webhooks reference](https://formstep.io/docs/developers/webhooks-reference/#fields-vs-answers) for both. Abandoned submissions arrive hours later, so **Execute step** cannot catch one. For that event, publish the workflow and check **Executions**. ## 3. Pick out the answers Add an **Edit Fields (Set)** node after the trigger. Drag answers from the input panel into **Fields to Set**, or type the expressions yourself: ![An Edit Fields node with three fields: company from $json.data.answers.company\_name, email from $json.data.answers.contact\_email, and payment\_terms from $json.data.display.do\_you\_accept\_our\_30\_day\_payment\_terms, with the output Nordlys Tekstil AS, grace@nordlys.example and Yes](https://formstep.io/docs/guides/n8n/n8n-map-fields.png) payment\_terms reads from display, so it holds the label Yes rather than the option key yes. The next node, say Gmail or Google Sheets, reads `{{ $json.company }}` and the other fields from here. When a question is renamed, its field key stays the same, so these expressions keep working. ## 4. Publish and check a real run Click **Publish**, give the version a name, and confirm. Formstep now sends every new submission to the workflow’s production URL. Unpublishing the workflow stops that. Submit the form once more and open the **Executions** tab. The run appears within seconds. Runs you started with **Execute step** carry a flask icon; a production run has none: ![The Executions tab of the published workflow: the latest run Succeeded in 48ms with no flask icon, three earlier test runs with a flask icon, and the canvas showing Formstep Trigger passing 1 item to Edit Fields](https://formstep.io/docs/guides/n8n/n8n-production-execution.png) ## If the workflow does not start - A submission through a request link runs **Request Completed**, not Public Link Submission Created. - While the workflow is a draft, only **Execute step** listens, and only until the first event arrives or two minutes pass. Publish the workflow. - If the trigger shows _Formstep sends events to HTTPS addresses only_, your n8n hands out an `http://` or private address. See [Tell n8n its public address](https://formstep.io/docs/guides/n8n/install-the-node/#public-address). - The trigger listens to the form in the workspace its credential points at. Check that it uses the right credential. - The trigger rejects events when the n8n host’s clock is more than five minutes off. Keep the clock accurate. ## Next steps [Send a request and act on its outcome](https://formstep.io/docs/guides/n8n/send-a-request/) — Create a request, then branch on what the recipient decided [Integrations overview](https://formstep.io/docs/integrations/overview/) — Slack, Sheets and Notion without n8n # Send a request and act on its outcome Create a Formstep request from an n8n workflow, prefill and lock what you already know, then wait for the answer in the same workflow or start another one when a request ends. ## Before you start You need a [Formstep credential in n8n](https://formstep.io/docs/guides/n8n/connect/) and a published form. To hear back from Formstep, your n8n must be reachable from the internet over HTTPS; see [Install the Formstep node in n8n](https://formstep.io/docs/guides/n8n/install-the-node/). What a request is, and what it can carry, is in [Creating requests](https://formstep.io/docs/requests/creating-requests/). For a full walkthrough with real payloads, read [Pausing an n8n workflow until the customer confirms their details](https://formstep.io/blog/n8n-wait-for-customer/). For what the node is good for, and how it compares with n8n’s own form, see [Formstep for n8n](https://formstep.io/integrations/n8n/). ## 1. Add the Create request node Start with the trigger that should send the request, such as a new row in a sheet or a deal marked won in your CRM. The example uses a manual trigger and an **Edit Fields** node named _Supplier_ that holds `supplier_id`, `company_name` and `contact_email`. Click **+** after it, search for **Formstep**, and pick **Create request**. ## 2. Pick the form and the recipient - **Form** is the form the recipient completes. Pick it from the list, or switch to **By ID** and paste its ID. It must be published. - **Recipient Email** usually comes from the input, like `{{ $json.contact_email }}`. It is needed for email delivery and for reminders. ![The Create request node: input from Supplier on the left; Form \[Guide\] Supplier onboarding, Recipient Email {{ $json.contact\_email }}, and Values to Send with company\_name and contact\_email mapped from the input; on the right the output with deduplicated true, deliveryStatus not\_requested, externalId supplier-SUP-1042, status pending and a url on form.formstep.io](https://formstep.io/docs/guides/n8n/n8n-create-request.png) ## 3. Prefill and lock what you already know Once you pick the form, **Values to Send** lists every question you can prefill by its title and [field key](https://formstep.io/docs/requests/field-keys/), like _Company name (company\_name)_. Map the answers you already have; fields left empty are not sent. With **Mapping Column Mode** set to **Map Automatically**, every input field named after a field key is sent. ![Further down the Create request node: Read-Only Field Names or IDs with Company name (company\_name), an empty Documents list, the Wait for the Outcome switch off, and Additional Fields with Delivery None, External ID supplier-{{ $json.supplier\_id }}, Recipient Name Grace Hansen and Test Mode on](https://formstep.io/docs/guides/n8n/n8n-create-request-options.png) Pick a question under **Read-Only Field Names or IDs** to lock it. Fields marked _· context_ are the form’s hidden fields. If the form has a Documents block, **Documents** takes files from a binary field of the input item. What each of these does is in [Prefill, locked fields and hidden fields](https://formstep.io/docs/requests/creating-requests/#three-buckets). ## 4. Add the options you need Click **Add Field** under **Additional Fields**: - **Delivery** decides who sends the link. **Email the Invitation** lets Formstep send the [invitation and reminders](https://formstep.io/docs/requests/invitations-and-reminders/), on Pro and Business. **None**, the default, sends no invitation: a later node sends the request’s `url` by Slack, SMS or your own email tool. - **Reminders** left out follows the form’s reminder schedule, and those reminders are emailed even when Delivery is None. Add **Reminders** and leave it empty to send none, or enter a schedule like `2d, 5d`, on Pro and Business. - **External ID** is your own ID for this piece of work, like `supplier-{{ $json.supplier_id }}`. A later workflow can [find the request by it](https://formstep.io/docs/guides/n8n/manage-requests/#find-a-request). Running the node again with the same External ID and the same parameters returns the same request instead of a second one. - **Test Mode** creates a [test request](https://formstep.io/docs/requests/creating-requests/): nothing is emailed and it counts nowhere. - **Recipient Name**, **Language**, **Expires At** and **Metadata** are optional. - **Callback URL** sends the outcome to a URL of your own. To get the outcome back into this workflow, use [Wait for the Outcome](#wait-for-the-outcome) instead. ## 5. Run the node Click **Execute step**. Formstep creates the request and returns it with its `id`, `status` `pending`, and `url`, the link for the recipient. Map `{{ $json.url }}` into the node that sends it. > **Running the node creates a real request** > > Unless **Test Mode** is on, every run, including **Execute step** in the editor, is a real request: with email delivery the recipient gets the invitation. Turn Test Mode on while you build the workflow. Before you publish, turn it off and change the External ID, because the old one belongs to the test request. ## Act on the outcome n8n can hear about a request’s outcome in two ways. Both end in a Switch on `approve`, `decline` or `changes`, which needs a [Decision question](https://formstep.io/docs/requests/decisions-and-approvals/) in the form. | Way | Use it when | | - | - | | Wait for the Outcome | The workflow that creates the request should carry on with the answer, holding on to everything it knew before, like the record it was working on. | | Formstep Trigger | Requests to the form are sent from anywhere: the dashboard, the API, an agent or another workflow. One published workflow handles all of them. | ## Wait for the outcome in the same workflow Here the workflow that sends the request pauses until the recipient answers, then carries on. ### 1. Turn on Wait for the Outcome In the **Create request** node, turn on **Wait for the Outcome** and leave **Callback URL** empty. Formstep then calls this execution back when the request ends. Each execution waits on its own address, so an External ID reused from an earlier execution fails. Include `{{ $execution.id }}` in it, or leave it empty. ### 2. Add a Wait node Add a **Wait** node right after it, with **Resume** set to **On Webhook Call** and the HTTP method left at `POST`. Set **Limit Wait Time** if you want n8n to give up on its own. ### 3. Branch on the event Add a **Switch** after the Wait node and compare `{{ $json.body.data.request.outcome }}`. To tell a completed request from an expired or canceled one, compare `{{ $json.body.type }}` with `request.completed`, `request.expired` or `request.canceled`. On `request.completed`, `{{ $json.body.data.answers.iban }}` reads the confirmed value of one answer. The part after `answers.` is the question’s [field key](https://formstep.io/docs/requests/field-keys/), which stays the same when you retitle, translate or duplicate the question. Run the workflow. The execution stops at the Wait node until the recipient submits, then resumes down the matching branch: ![A finished execution: When clicking Execute workflow, Create request, Wait for the answer and a Switch named Outcome, all green, with 1 item leaving the approve output](https://formstep.io/docs/guides/n8n/n8n-wait-for-outcome.png) - A run from the editor waits for a real answer too. Test Mode works here: a test request still resumes the workflow. - Expiry also resumes the execution, with `request.expired`. Set **Expires At** to end the wait sooner. - The Wait node relies on its resume address being hard to guess. If that is not enough, use the Formstep Trigger, which checks that every event comes from Formstep. ## Start a workflow from the trigger ### 1. Pick the event Add a **Formstep Trigger**, pick the credential and the form, and choose one of the request events under **Event**: | Event | Runs when | Carries | | - | - | - | | Request Completed | The recipient submits the form. | The request, the outcome and every answer. | | Request Expired | The request reaches its expiry date unanswered. | The request only. | | Request Canceled | Someone cancels the request, from the dashboard, the API or a Cancel request node, or Formstep cancels it because the form was moved to trash. | The request and the cancel reason. | ### 2. Test it with a real request A test request never starts a request trigger, so send a real one with Delivery set to None, as in [step 5](#run-the-node), and open its link yourself. Then click **Execute step** and submit the form within two minutes. The event carries a **data.request** object next to the answers. Its `outcome` is `approve`, `decline` or `changes`, and its `externalId` is the ID you set, so you can find the record the request was about. Expired and canceled events are hard to catch with **Execute step**. Publish the workflow and check **Executions** instead. ### 3. Branch on the outcome Add a **Switch** node with one routing rule per verdict. Each rule compares `{{ $json.data.request.outcome }}` with a value; turn on **Rename Output** to name the branch after it: ![A Switch node named Outcome: the input shows data › request with outcome approve, externalId approval-8 and the recipient Grace Hansen; the routing rules compare {{ $json.data.request.outcome }} with approve and changes, each with a renamed output](https://formstep.io/docs/guides/n8n/n8n-route-outcome.png) Branch on `data.request.outcome`. `data.answers.decision` is empty when the request expired or was canceled, so it cannot tell those branches apart. Compare values, never the labels in `data.display`, which change when you rename the option or the recipient answers in another language. Connect a node to each branch, then **Publish** the workflow. From then on every completed request to the form runs it. ## If nothing runs - A test request never starts a request trigger, whether it was created with Test Mode on or with **Try it yourself**. - The trigger listens only to the form you chose in it. - The workflow is not published, or its credential reaches another workspace. See the checks in [the public link guide](https://formstep.io/docs/guides/n8n/public-link-submissions/#troubleshooting). - Create request fails with _Formstep calls back HTTPS addresses only_ when your n8n hands out an `http://` address. See [Tell n8n its public address](https://formstep.io/docs/guides/n8n/install-the-node/#public-address). - The Wait node never resumes when Formstep cannot reach your n8n, for example because the tunnel is down or the proxy blocks `/webhook-waiting/`. ## Next steps [Look up, remind and cancel requests in n8n](https://formstep.io/docs/guides/n8n/manage-requests/) — Find a request by External ID, remind the recipient, or withdraw it [Get a manager's approval in n8n](https://formstep.io/docs/guides/n8n/approval-from-a-manager/) — Send a claim to a manager, wait for the decision, route each outcome [n8n workflow templates](https://formstep.io/workflows/) — Three workflows to import: CRM prefill, PDF reading with AI, and onboarding [Form templates](https://formstep.io/templates/) — Forms to start from, each with its field keys listed # Look up, remind and cancel requests in n8n Find a request by your own External ID, read its status and answers, send the recipient a reminder, or cancel it, all from an n8n workflow. ## Before you start You need a [Formstep credential in n8n](https://formstep.io/docs/guides/n8n/connect/) and a request created with an External ID, as in [Send a request and act on its outcome](https://formstep.io/docs/guides/n8n/send-a-request/). The same actions exist in the dashboard; see [The Requests page](https://formstep.io/docs/requests/managing-requests/). ## Find a request with Get many requests Your system knows the supplier as `SUP-1042`, not by Formstep’s request ID. Add a Formstep node, pick **Get many requests**, and filter by the External ID you set when you created the request: ![The Get many requests node: Operation Get Many, Return All off, Limit 50, Scope Workspace, and Filters with External ID supplier-SUP-1042, Include Test Requests on and Status Pending](https://formstep.io/docs/guides/n8n/n8n-find-request.png) - **Scope** is **Form**, the default, which then asks for the form, or **Workspace**, every request the credential can reach. - **Filters** narrow the list by **External ID**, **Status** or **Outcome**. **Include Test Requests** is off by default, so a request created with Test Mode on is not found. Turn it on while you build the workflow with test requests. - The newest request comes first. **Limit** caps the number of items; **Return All** fetches every page. Each request is one output item, so the nodes after it run once per request. When nothing matches, the node outputs no items and the workflow stops there; turn on **Always Output Data** in the node’s settings to carry on. Every node below takes the request as **Request** › **By ID** `{{ $json.id }}`, or pick one of the newest requests under **From list**. ## Read a request with Get request **Get request** returns the request. **Output** sets how much of it comes back, and **Get many requests**, **Remind request recipient** and **Cancel request** have it too: - **Simplified**, the default, returns ten fields: `id`, `status`, `outcome`, `externalId`, `formId`, `recipientEmail`, `answers` once it is completed, as `{{ $json.answers.company_name }}`, and `createdAt`, `expiresAt` and `completedAt` as ISO 8601 dates. Only Get request has answers; the other operations return the remaining nine fields. - **Raw** returns the whole request: what you prefilled and locked, the documents, the reminders sent, the timeline, and `display`, the answers as text. Its timestamps are Unix time in milliseconds. - **Selected Fields** returns the request ID and the fields you pick under **Fields**, with the same values as Raw, timestamps included. A field the response does not have is left out: Answers, Display and Timeline come with Get request only, and Form Name and Form Origin with Get many requests only. **Output** came with version 0.15.0 of the package. A node saved earlier keeps its old **Simplify** switch, or returns the whole request. ## Send a reminder with Remind request recipient **Remind request recipient** emails the recipient a reminder now, on Pro and Business. Scheduled reminders still go out as planned. The request must be pending and have a recipient email, and a test request is never reminded. How often you can remind someone is in [Invitations, reminders and expiry](https://formstep.io/docs/requests/invitations-and-reminders/). ## Withdraw a request with Cancel request **Cancel request** withdraws a pending request. The link stops working, and the recipient sees a withdrawn notice instead of the form. Fill in **Reason** to record why: it comes in the [Request Canceled](https://formstep.io/docs/guides/n8n/send-a-request/#trigger) event, so another workflow can tell your team. To see it in this node’s output, set **Output** to **Raw**. A request that is already completed, expired or canceled stays as it is, and the node fails. ## Send the callback again **Replay request callback** sends a finished request’s callback URL the outcome again, for when your receiver missed it. Developers: see [When a callback never lands](https://formstep.io/docs/developers/callbacks/#replay). ## Next steps [The Requests page](https://formstep.io/docs/requests/managing-requests/) — The same actions in the dashboard [Automation guides](https://formstep.io/docs/guides/overview/) — Every guide for Zapier, Make, n8n and AI agents # Get a manager's approval in n8n Example: send an expense claim to the manager who approves it, pause the workflow until they decide, then route each outcome and no answer. The same workflow approves anything one person must sign off: a discount for a deal, a purchase order, a holiday, a contract change. Swap the expense for your own data; the steps stay the same. Checked on self-hosted n8n 2.44 with version 0.13.0 of the node. ![The finished execution: When clicking Execute workflow, Expense claim, Download the receipt, Ask the manager, Wait for the decision and Route by decision, all green, with one item leaving the approve output to Approved: pay it out. The changes, decline and no answer outputs lead to their own nodes.](https://formstep.io/docs/guides/n8n/n8n-approval-workflow.png) ## Before you start - The Formstep node on an n8n with a public HTTPS address, and a [Formstep credential](https://formstep.io/docs/guides/n8n/connect/). See [Install the Formstep node in n8n](https://formstep.io/docs/guides/n8n/install-the-node/). - Pro or Business to email the request to the manager. On Free, leave **Delivery** at None and send the request’s link yourself. Import the whole workflow from [formstep-expense-approval.json](https://github.com/formstep/n8n-nodes-formstep/blob/main/examples/formstep-expense-approval.json) (**Workflows** › **Import from File**), then pick your credential and your form in **Ask the manager**. ## 1. Build the approval form In Formstep, open **Templates**, pick **Approval** and click **Use template**. The template has a [Decision question](https://formstep.io/docs/requests/decisions-and-approvals/) with Approve, Decline and Request changes, a text box that shows when the manager asks for changes, and a signature that shows when they approve. Then make it about the expense: - Add [hidden fields](https://formstep.io/docs/building-forms/hidden-fields/) for what the manager must see but never edit: `reference` (the template has it), `employee_name`, `amount` and `description`. - Write them into the text with **@**, for example _@employee\_name asks you to approve an expense of @amount for @description, reference @reference._ - Add a [Documents block](https://formstep.io/docs/building-forms/documents-block/) named Receipt. The workflow attaches the receipt there for this manager only. Publish the form. This is what the manager opens: ![The request page: Approve an expense, Grace Hansen asks you to approve an expense of NOK 3 700 for Hotel in Bergen, client workshop, reference EXP-1042, the attached Receipt EXP-1042 PDF, Your Name filled in with Maria Berg, Company locked to Acme Inc, and the Approve, Decline and Request changes options](https://formstep.io/docs/guides/n8n/n8n-approval-recipient.png) ## 2. Bring in the expense and its receipt The example starts with an **Edit Fields** node named _Expense claim_ that holds the claim: `expenseId`, `employeeName`, `employeeEmail`, `managerName`, `managerEmail`, `amount`, `description` and `receiptUrl`. In your workflow, the claim comes from wherever it starts, such as a webhook from your expense tool or a Formstep public link form the employee fills in. An **HTTP Request** node named _Download the receipt_ fetches `{{ $json.receiptUrl }}` with **Response Format** set to **File**. The file lands in the binary field `data`. If the receipt arrives as an email attachment or a Google Drive file, use that node instead; anything that puts the file in a binary field works. ## 3. Ask the manager Add a Formstep node, pick **Create request** and choose the approval form. Read the claim from the _Expense claim_ node, as `{{ $('Expense claim').item.json.managerEmail }}`: after the Wait node, `$json` is Formstep’s event, and the same expression still finds the claim. ![The Ask the manager node: Form Expense approval, Recipient Email from the Expense claim node, Values to Send with reference, amount, description and employee\_name marked context, Your Name and Company Acme Inc, Company locked under Read-Only Field Names or IDs, a document from the binary field data named Receipt with the expense ID in the Automatic Documents block, and Wait for the Outcome on](https://formstep.io/docs/guides/n8n/n8n-approval-request-node.png) - **Values to Send** lists the hidden fields marked _· context_: the manager sees them in the text and cannot change them. **Your Name** and **Company** are prefilled, and **Read-Only Field Names or IDs** locks the company. See [Prefill, locked fields, and context](https://formstep.io/docs/requests/creating-requests/#three-buckets). - Under **Documents**, the document takes the binary field `data`. **Automatic** puts it in the form’s only Documents block; pick a block when the form has several. - Turn on **Wait for the Outcome**. Formstep then calls this execution back when the manager answers. Under **Additional Fields**: - **Delivery** _Email the Invitation_ emails the manager the link. The request’s reminders follow the form’s schedule. - **Expires At** `{{ $now.plus({ days: 3 }).toISO() }}` ends the wait after three days. - **External ID** `{{ $('Expense claim').item.json.expenseId }}-{{ $execution.id }}` ties the request to the claim. The execution ID keeps it unique: every execution has its own resume URL. - **Test Mode**, on in the example, sends nothing and keeps the request out of your lists. Turn it off when the workflow goes live. ## 4. Wait and route the decision Add a **Wait** node with **Resume** set to **On Webhook Call**. Then add a **Switch** node with three routing rules, each comparing `{{ $json.body.data.request.outcome }}` with `approve`, `changes` or `decline`, and each with **Rename Output** on. Under **Options**, set **Fallback Output** to **Extra Output** and name it _no answer_: an expired or canceled request has no outcome and goes there. Each branch reads what it needs from the event and the claim: ```text Who decided: {{ $json.body.data.answers.full_name }} What should change: {{ $json.body.data.answers.requested_changes }} Why it ended: {{ $json.body.type }} (request.completed, request.expired or request.canceled) The claim: {{ $('Expense claim').item.json.expenseId }} ``` The example ends each branch in an **Edit Fields** node that writes the message. Put the node that sends it in its place: Gmail or Slack to the employee, your accounting tool for the payout, a second request to finance for _no answer_. ## 5. Run it Click **Execute workflow**. The execution stops at the Wait node. Open the request’s `url` from the output of _Ask the manager_, decide, and submit: the execution resumes down the matching branch, as in the picture at the top. With Test Mode on, the callback still resumes the workflow; only the email stays unsent. > **Two people, two steps** > > Chain two of these for a two-step approval: when the manager approves, a second Create request node sends the same claim to finance, with the manager’s name as context, and waits again. Each step keeps its own decision. ## Next steps [Start a workflow from the trigger](https://formstep.io/docs/guides/n8n/send-a-request/#trigger) — Use a Formstep Trigger instead of waiting, for requests sent from anywhere [Read a PDF with AI, then get it confirmed](https://formstep.io/docs/guides/n8n/read-a-pdf-with-ai/) — Let AI fill the form, and the person who knows check it # Read a PDF with AI, then get it confirmed Example: an AI model reads a supplier invoice in n8n, a request prefilled with what it read goes to the supplier, and they confirm or correct each value. The pattern fits any document someone sends you: an order, a claim, an application, a contract to check. Checked on self-hosted n8n 2.44 with version 0.13.0 of the node. ![The finished execution: Invoice, Download the invoice, Extract the text, Read the invoice with AI with its Claude model, Keep the file, Ask the supplier to confirm, Wait for the supplier, Confirmed? and What the supplier corrected, all green](https://formstep.io/docs/guides/n8n/n8n-invoice-workflow.png) ## Before you start - The Formstep node on a self-hosted n8n with a public HTTPS address, and a [Formstep credential](https://formstep.io/docs/guides/n8n/connect/). On n8n Cloud, call the REST API from an HTTP Request node instead; see [n8n Cloud](https://formstep.io/docs/guides/n8n/install-the-node/#n8n-cloud). - A credential for a chat model in n8n: Anthropic, OpenAI, Google Gemini or any other the AI nodes take. - A form for the confirmation. In Formstep, open **Templates**, pick **Confirm Invoice Details**, click **Use template** and publish it. Its field keys are `supplier_name`, `invoice_number`, `invoice_date`, `due_date`, `total_amount`, `confirmed` and `notes`, and it has a Documents block for the invoice. Import the workflow from [formstep-invoice-ai-confirm.json](https://github.com/formstep/n8n-nodes-formstep/blob/main/examples/formstep-invoice-ai-confirm.json) with **Import from File**. The example pins a sample result on the AI node, so it runs before you add a model; unpin it once your model is connected. ## 1. Get the PDF The example downloads a [sample invoice](https://formstep.io/docs/guides/n8n/samples/invoice.pdf) with an **HTTP Request** node, **Response Format** **File**, and takes the supplier’s address from an **Edit Fields** node named _Invoice_. In your workflow it is usually a Gmail or Outlook trigger on the invoices inbox, with the attachment in a binary field and the sender as the recipient, or a new file in a Google Drive folder. ## 2. Extract the text Add an **Extract from File** node with **Operation** **Extract From PDF**. It reads the PDF’s text into `{{ $json.text }}`. > **Scanned documents** > > Extract From PDF reads the text a PDF carries. A scan or a photo has none. For those, use a model node that reads images or PDFs itself instead of these two steps; the later steps stay the same. If you get both kinds, check the length of `text` in an **If** node and route the empty ones to that path. ## 3. Read it with AI Add an **Information Extractor** node, connect your chat model to it, and set **Text** to `{{ $json.text }}`. Under **Attributes**, add one attribute per value you want, named after the field key it fills: | Name | Type | Description | | - | - | - | | supplier\_name | String | The company that issued the invoice | | invoice\_number | String | The invoice number, as printed | | invoice\_date | Date | The invoice date, as YYYY-MM-DD | | due\_date | Date | The payment due date, as YYYY-MM-DD | | total\_amount | Number | The total to pay, including VAT, as a number without currency | ![The Information Extractor node: Text {{ $json.text }}, Schema Type From Attribute Descriptions, and the first two attributes supplier\_name and invoice\_number open with their type, description and Required on](https://formstep.io/docs/guides/n8n/n8n-invoice-extractor.png) The node returns what it read under `{{ $json.output }}`, as `{{ $json.output.total_amount }}`. Naming the attributes after the field keys keeps the mapping in the next step one to one. ## 4. Put the file back The Information Extractor returns what it read, not the PDF. To attach the invoice to the request, add a **Merge** node with **Mode** **Combine** and **Combine By** **Position**. Connect the HTTP Request node to input 1 and the Information Extractor to input 2. Each item then has the file in `data` and the reading in `output`. ## 5. Ask the supplier to confirm Add a Formstep **Create request** node and pick the Confirm Invoice Details form. Set **Recipient Email** to the supplier’s address, map each field under **Values to Send** to its value, like `{{ $json.output.invoice_number }}`, and add a document from the binary field `data`, named `Invoice {{ $json.output.invoice_number }}`. Turn on **Wait for the Outcome** and add a **Wait** node after it, **Resume** **On Webhook Call**. Dates and the amount arrive in their own inputs: the date fields take `2026-10-20`, the amount a number. The supplier sees every value filled in, with the invoice above them, and fixes what is wrong: ![The request page: Check Your Invoice Details, the attached Invoice INV-2026-0187 PDF, Supplier Fjord Office Supplies AS, Invoice Number INV-2026-0187, Invoice Date October 20, 2026, Due Date November 3, 2026, Total Amount 27,235, and the confirmation switch](https://formstep.io/docs/guides/n8n/n8n-invoice-recipient.png) Formstep emailing the supplier needs Pro or Business. On Free, leave **Delivery** at **None** and send the request’s `url` with your own email node; see [Invitations and reminders](https://formstep.io/docs/requests/invitations-and-reminders/). The request expires after 30 days unless you set **Expires At**, so set it sooner for an invoice due in two weeks; see [Expiry](https://formstep.io/docs/requests/invitations-and-reminders/#expiry). ## 6. See what was corrected Add an **If** node that checks `{{ $json.body.type }}` is `request.completed`, and on its true output a **Code** node that compares the AI’s reading with the confirmed answers: ```javascript // Compare what the AI read with what the supplier confirmed. const read = $('Read the invoice with AI').item.json.output const confirmed = $json.body.data.answers const corrected = Object.keys(read).filter((key) => String(read[key]) !== String(confirmed[key])) return { json: { invoice: confirmed, corrected, notes: confirmed.notes ?? null } } ``` In the test run the model read the total as 27 235. The supplier corrected it to 27 325 and left a note, so `corrected` lists `total_amount`: ![The output of What the supplier corrected: invoice with confirmed true, the dates, the invoice number, the supplier's note, supplier\_name and total\_amount 27325, and corrected with one entry, total\_amount](https://formstep.io/docs/guides/n8n/n8n-invoice-corrected.png) From here, book the invoice with `invoice` in your accounting tool, and send the ones where `corrected` is not empty to a person for a second look. The false output of the If node catches a request that expired: chase the supplier there. ## Variations - For documents nobody outside should see again, send the request to someone on your team rather than the sender. - Lock what you are sure of. Values from your own system, like the order number you issued, go under **Read-Only Field Names or IDs**, so only the AI’s reading is open to correction. - An If node before the request can send readings that fail the arithmetic to your team instead of the supplier. - With no form yet, set **Form Source** to **New Form** and build the form from the attributes; see [Collect the documents each case needs](https://formstep.io/docs/guides/n8n/collect-documents/). To show the invoice above the values, use a published form. ## Next steps [Collect the documents each case needs in n8n](https://formstep.io/docs/guides/n8n/collect-documents/) — Build the form from a checklist and file every upload [Value shapes](https://formstep.io/docs/developers/requests-api/#value-shapes) — What each field type takes as a prefilled value [The workflow, ready to import](https://formstep.io/workflows/n8n-pdf-extraction-template/) — The same flow as an n8n workflow JSON, with a sample invoice [Invoice verification template](https://formstep.io/templates/confirm-invoice-details/) — The form the workflow prefills, with its field keys # Collect the documents each case needs in n8n Example: build the form from an insurance claim's checklist inside the request, so each person is asked for exactly the documents their case needs, then save each upload with the claim. The same steps work for onboarding, loans, rentals, visas: anything where the paperwork depends on the case. Checked on self-hosted n8n 2.44 with version 0.13.0 of the node. ![The finished execution: Claim, Build the checklist, Ask for the documents, Wait for the documents and Completed?, then One item per file, Download each file and Save to the claim folder with 2 items each, all green](https://formstep.io/docs/guides/n8n/n8n-claim-workflow.png) ## Before you start - The Formstep node on an n8n with a public HTTPS address, and a [Formstep credential](https://formstep.io/docs/guides/n8n/connect/). - Somewhere to keep the files. The example writes them to n8n’s disk; the last step shows Google Drive instead. The node is for self-hosted n8n only: n8n Cloud installs only verified community nodes, and ours is not verified yet. See [Install the Formstep node](https://formstep.io/docs/guides/n8n/install-the-node/#n8n-cloud). Import the workflow from [formstep-claim-documents.json](https://github.com/formstep/n8n-nodes-formstep/blob/main/examples/formstep-claim-documents.json) with **Import from File**. It needs no form in Formstep: the request builds one. ## 1. Start from the case The example starts with an **Edit Fields** node named _Claim_: `claimId`, `claimType` ( `water_damage` or `theft`), `claimLabel`, `firstName` and `email`. In your workflow, it is the record that opens the case, such as a CRM deal or a webhook from your claims system. ## 2. Turn the checklist into blocks A **Code** node named _Build the checklist_ looks up the documents for the claim type and writes the form as blocks: a greeting, two [hidden fields](https://formstep.io/docs/building-forms/hidden-fields/) the greeting reads, one file question per document, and a note. ```javascript const checklists = { water_damage: [ { key: 'damage_photos', title: 'Photos of the damage', maxFiles: 10 }, { key: 'repair_estimate', title: 'Repair estimate from a contractor', maxFiles: 1 }, ], theft: [ { key: 'police_report', title: 'Police report', maxFiles: 1 }, { key: 'receipts', title: 'Receipts for the stolen items', maxFiles: 10 }, ], } const documents = checklists[$json.claimType] if (!documents) throw new Error(`No checklist for claim type ${$json.claimType}`) const blocks = [ { type: 'header', content: 'Hi {{first_name}}, a few documents for claim {{claim_id}}' }, { type: 'paragraph', content: 'Upload each document below. Photos from your phone are fine.' }, { type: 'hiddenField', key: 'first_name' }, { type: 'hiddenField', key: 'claim_id' }, ...documents.map((document) => ({ type: 'file', ...document })), { type: 'text', key: 'notes', title: 'Anything we should know?', required: false }, ] return { json: { ...$json, blocks } } ``` The `throw` stops the run on an unknown claim type instead of sending a form with no questions. Adding a claim type is a new entry in the object. Each block takes `type`, a `key` and the options of its kind; `{{first_name}}` in text shows a hidden field’s value. Every block type and option is in [Create a form and a request in one call](https://formstep.io/docs/developers/inline-forms/#blocks). ## 3. Send the request with a new form Add a Formstep **Create request** node and set **Form Source** to **New Form**: ![The Ask for the documents node: Form Source New Form, Form Name Documents for your {{ $json.claimLabel }} claim, Form Blocks {{ JSON.stringify($json.blocks) }}, Prefill {}, Context {{ JSON.stringify({ first\_name: $json.firstName, claim\_id: $json.claimId }) }}, Read-Only Field Keys empty, and under Form Options a Cover with a color](https://formstep.io/docs/guides/n8n/n8n-claim-request-node.png) - **Form Blocks** is `{{ JSON.stringify($json.blocks) }}`, the blocks from the Code node. - **Context** fills the hidden fields: `{{ JSON.stringify({ first_name: $json.firstName, claim_id: $json.claimId }) }}`. - **Form Options** sets the look: a cover color, a logo and a theme. - Set **Recipient Email**, turn on **Wait for the Outcome**, and under **Additional Fields** set **Delivery**, **Expires At** (`{{ $now.plus({ days: 14 }).toISO() }}`) and an **External ID**. **Test Mode** is on in the example. Email invitations and reminders have plan requirements, and on Free you send the request’s `url` yourself and chase by hand: see [Invitations and reminders](https://formstep.io/docs/requests/invitations-and-reminders/). With Test Mode on, nothing is emailed until you turn it off: see [Try it on yourself first](https://formstep.io/docs/requests/creating-requests/#test-mode). The customer sees only the documents their claim needs: ![The request page: Hi Ada, a few documents for claim CLM-3107, Upload each document below, a file upload for Photos of the damage with 10 files remaining, one for the Repair estimate from a contractor with 1 file remaining, and an optional note](https://formstep.io/docs/guides/n8n/n8n-claim-recipient.png) > **One checklist, one form** > > Formstep builds a form the first time it sees a definition and reuses it after that: the second water damage claim reuses the first one’s form, and the response says `form.reused: true`. Keep what changes per person, like the name and the claim ID, in **Context** or **Prefill**, never in the blocks or the form name, or every request makes a new form. A workspace makes at most 300 new forms a day this way. See [One definition, one form](https://formstep.io/docs/developers/inline-forms/#reuse). ## 4. Wait, then make one item per file Add a **Wait** node (**On Webhook Call**) and an **If** node that checks `{{ $json.body.type }}` is `request.completed`. Each file question’s answer is a list of files with a `name`, a `size`, a `type` and a `url`. The other output of the **If** node catches an expired or canceled request. A request expires after 30 days unless you set another date, up to 365: see [Expiry](https://formstep.io/docs/requests/invitations-and-reminders/#expiry). A **Code** node turns the files into one item per file: ```javascript // One item per uploaded file, named after the question it answers. const event = $json.body.data const items = [] for (const [question, answer] of Object.entries(event.answers)) { if (!Array.isArray(answer)) continue for (const file of answer) { if (file.url) items.push({ json: { question, name: `${question} - ${file.name}`, url: file.url } }) } } return items ``` It walks the answers instead of naming the questions, so it works for any claim type. ## 5. Download and save each file An **HTTP Request** node downloads `{{ $json.url }}` with **Response Format** **File**, once per item. The example then writes each file with **Read/Write Files from Disk**, as `/home/node/.n8n-files/{{ $('Claim').item.json.claimId }} {{ $json.name }}`. To keep them in Google Drive instead, replace that node with **Google Drive** › **Upload file**: Input Data Field Name `data`, File Name `{{ $json.name }}`, and the claim’s folder as Parent Folder. S3, Dropbox and SharePoint nodes take the binary field the same way. The files went to Formstep, so your form’s payload limit never applied, and each can be up to 1 GiB within your storage: see [File upload](https://formstep.io/docs/building-forms/file-uploads-signatures/#file-upload). > **Treat a file URL like a password** > > Anyone with a file’s `url` can download it, so do not post it in a channel or a ticket. Download the file, as here, and share your own copy. The URL does not expire. - The event’s `data.submission.pdfUrl`, a PDF of all the answers, is set only when the form keeps a PDF: when it has a Signature question or a webhook subscription. A request that only calls back, as here, has none. - To send the recipient a document to read or sign as well, use a published form with a Documents block, as in [Get a manager’s approval](https://formstep.io/docs/guides/n8n/approval-from-a-manager/). A new form takes no documents. ## Next steps [Create a form and a request in one call](https://formstep.io/docs/developers/inline-forms/) — Every block type, option and limit of a new form [Read a PDF with AI, then get it confirmed](https://formstep.io/docs/guides/n8n/read-a-pdf-with-ai/) — Let AI fill the form, and the person who knows check it [Let an AI agent send requests from n8n](https://formstep.io/docs/guides/n8n/ai-agent-tool/) — Give an AI Agent Formstep as a tool [Document collection template](https://formstep.io/templates/document-collection/) — A published form with one labelled upload per file, for cases that share a checklist # Let an AI agent send requests from n8n Example: an onboarding assistant in n8n that has the Formstep node as a tool, so it can send a client a prefilled form, check how the request is going, and remind them. The agent decides when to ask someone and what to fill in; Formstep collects the answers and checks them against the form. Checked on self-hosted n8n 2.44 with version 0.13.0 of the node, by running each tool from n8n’s tool test. **Output**, in step 3, came with version 0.15.0. The conversation itself depends on the model you connect. ![The workflow: When chat message received into the Onboarding assistant agent, with Claude as its chat model, Simple Memory as its memory, and three Formstep tools: Send the onboarding form, Check an onboarding and Remind the client](https://formstep.io/docs/guides/n8n/n8n-agent-workflow.png) ## Before you start - The Formstep node and a [Formstep credential](https://formstep.io/docs/guides/n8n/connect/). n8n 2.46 lists the node as a tool without further setup. If it does not show up as a tool on an older self-hosted n8n, start n8n with `N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true`. - A credential for a chat model in n8n. - A form. In Formstep, open **Templates**, pick **Client Onboarding**, click **Use template** and publish it. It asks the client to check their details and sign, and has a hidden field `customer_id`. Import the workflow from [formstep-ai-agent-onboarding.json](https://github.com/formstep/n8n-nodes-formstep/blob/main/examples/formstep-ai-agent-onboarding.json) with **Import from File**, then pick your credentials and your form. ## 1. Set up the agent Add a **When chat message received** trigger and an **AI Agent** node. Connect your chat model and a **Simple Memory**, so the agent remembers the request it sent earlier in the chat. Under **Options** › **System Message**, tell it what it is for and where to stop: ```text You help the Acme Inc team onboard new clients. To onboard a client, send them the onboarding form with what you already know: their name, email, company and customer number. Ask the team member for anything missing first, and never invent an email address. After sending, give the team member the link and the request id. When asked how an onboarding is going, check the request. A completed request has the answers the client confirmed. Remind a client only when the team member asks you to. ``` ## 2. Add a tool that sends the form Click **+** under the agent’s **Tool** input, search for **Formstep** and pick **Create request**. Name the tool after what it does, here _Send the onboarding form_, and set **Tool Description** to **Set Manually** with a sentence the model reads to decide when to use it. ![The Send the onboarding form tool: its description, Resource Request, Operation Create, Form Source Published Form, Form Client Onboarding, Recipient Email and Values to Send customer\_id, Full Name and Company each filled by $fromAI with a key and a description](https://formstep.io/docs/guides/n8n/n8n-agent-tool.png) Pick the form yourself, so the agent can only send this one. For each value the agent should fill, click the ✨ button next to the parameter, or write `$fromAI()` with a key and a description the model reads: ```text Recipient Email {{ $fromAI('email', 'The client email address, exactly as the team member gave it', 'string') }} customer_id {{ $fromAI('customer_id', 'The customer number, such as C-1042', 'string') }} Full Name {{ $fromAI('full_name', 'The client contact full name', 'string') }} Company {{ $fromAI('company_name', 'The client company name', 'string') }} ``` - Use each key once per tool. Two parameters with the same key get the same value. - Leave out what the client should type themselves, or what the agent cannot know. They fill in the rest of the form. - Under **Additional Fields**, **Delivery** _Email the Invitation_ emails the client. **Test Mode**, on in the example, sends nothing while you try it out. ## 3. Add tools to check and remind Add two more Formstep tools on the same input: - _Check an onboarding_: **Get request**, with **Request** › **By ID** set to `{{ $fromAI('request_id', 'The id that Send the onboarding form returned for this client', 'string') }}`. **Output** **Simplified**, the default, returns ten fields: the status, the outcome, the client’s email, the dates, and the answers once the client completed the form. That keeps the model’s context small. For even less, set it to **Selected Fields** and pick what the agent needs, like **Status** and **Answers**: the tool returns those and the request ID. **Raw** returns the whole request. - _Remind the client_: **Remind request recipient**, with the same **Request**. A test request is never reminded, so this tool answers with an error until Test Mode is off. A new Remind or Cancel tool returns the simplified request unless you set **Output** to **Raw** or **Selected Fields**. New tools are version 5 of the node. Tools saved with version 3 or 4 keep **Simplify** on Get request and return the whole request from Remind and Cancel. See [Read a request with Get request](https://formstep.io/docs/guides/n8n/manage-requests/#get-a-request). ## 4. Test each tool without the model Open a tool and click **Execute step**. n8n asks for the values the model would send, one box per `$fromAI` key, then runs the tool. _Send the onboarding form_ returns the request’s `id` and `url`; put that `id` into _Check an onboarding_ to see what the agent will read. When a value is refused, the error the agent gets names it, such as _VALIDATION\_ERROR: “email” is not a valid email address_, so the model can ask again. ## 5. Chat with it Click **Open chat** and write, for example: _Onboard Analytical Engines Ltd, contact Ada Lovelace, , customer number C-1042._ The agent calls _Send the onboarding form_ and replies with the link. Later, _How is the Ada Lovelace onboarding going?_ makes it call _Check an onboarding_ with the request ID it kept in memory. > **Hear back without asking** > > The agent only knows what it checks. To act the moment a client completes the form, add a separate workflow with a [Formstep Trigger on Request Completed](https://formstep.io/docs/guides/n8n/send-a-request/#trigger) for the same form, and post the answers to your team or your CRM from there. - The tools reach only the workspace the credential connects, and only the forms you picked in them. - Agents outside n8n, like Claude or ChatGPT, can do the same through Formstep’s MCP server; see [Send a request with an AI agent](https://formstep.io/docs/guides/ai-agents/send-a-request/). ## Next steps [Start a workflow from the trigger](https://formstep.io/docs/guides/n8n/send-a-request/#trigger) — Start a workflow when the client completes the form [Look up, remind and cancel requests in n8n](https://formstep.io/docs/guides/n8n/manage-requests/) — Every request action, outside an agent # Connect an AI agent to Formstep Add Formstep to Claude, ChatGPT, Cursor or any tool that supports remote MCP servers, sign in, and pick the workspace the agent may use. ## Before you start - A Formstep account that is a member of the workspace you want to connect. - An AI tool that supports remote MCP servers. Some tools limit this to certain plans; the table below links to each tool’s own guide. An agent’s work spends no Formstep AI credits; your AI tool bills you under its own pricing. See [AI credits & usage](https://formstep.io/docs/ai/ai-credits-usage/#mcp). ## 1. Add the Formstep server to your AI tool Every tool asks for the same thing, the server URL: ```text https://api.formstep.io/api/mcp ``` Leave any client ID, secret or token field empty. The tool finds the sign-in page from the server and registers itself. Where to paste the URL depends on the tool: | Tool | Where to add the URL | Official guide | | - | - | - | | Claude (web, desktop and mobile) | **Customize** › **Connectors** › **+** › **Add custom connector**. A connector added on claude.ai also works in the desktop and mobile apps. On Team and Enterprise, an owner adds it first under **Organization settings** › **Connectors**; on Free you can add one custom connector. | [Anthropic](https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp) | | Claude Code | Run `claude mcp add --transport http formstep https://api.formstep.io/api/mcp`, then type `/mcp` in Claude Code to sign in. | [Anthropic](https://code.claude.com/docs/en/mcp) | | ChatGPT | Turn on developer mode under **Settings** › **Apps** › **Advanced settings**. Then open **Settings** › **Apps** › **Create**, paste the URL, choose OAuth, click **Scan Tools**, sign in to Formstep, and click **Create**. Developer mode works on the web only. On Pro, ChatGPT can only read: tools that change something, such as sending a request or editing a form, need Business, Enterprise or Edu. On Business, only admins and owners can use developer mode. | [OpenAI](https://help.openai.com/en/articles/12584461-developer-mode-and-mcp-apps-in-chatgpt) | | Cursor | Click [Add to Cursor](https://cursor.com/install-mcp?name=formstep\&config=eyJ1cmwiOiJodHRwczovL2FwaS5mb3Jtc3RlcC5pby9hcGkvbWNwIn0=). Or add `{"mcpServers": {"formstep": {"url": "https://api.formstep.io/api/mcp"}}}` to `.cursor/mcp.json` in your project, or to `~/.cursor/mcp.json` for every project. | [Cursor](https://cursor.com/docs/mcp) | Any other tool that supports remote MCP servers with sign-in works the same way: look for where it adds a server by URL. ## 2. Sign in and pick the workspace The first time the agent uses Formstep, your AI tool opens a Formstep page in the browser. Sign in if you are asked to. Formstep then shows which tool is asking, the account you are signed in as (**Not you?** switches it), what the tool may do, and the workspaces you belong to: ![The Formstep Authorize access page: Claude Code (formstep) wants to connect to your Formstep workspace, a note that Formstep has not verified the app, the account you are signed in as, a list of permissions, the Acme Inc workspace selected, and Deny and Authorize buttons](https://formstep.io/docs/guides/ai-agents/authorize-access.png) Pick the workspace and click **Authorize**. The browser returns to your AI tool. The connection reaches that one workspace only; to use another workspace, add Formstep to your tool a second time and pick the other one. ## 3. Check that it works Ask the agent something only Formstep can answer: ``` List my Formstep forms and whether each one is published. ``` The agent calls Formstep and answers with the forms in the workspace you picked. Some tools, like Claude Code, first say they are loading tool definitions: Formstep lists all its tools at once, and the tool fetches the details of the ones a task needs. > **Some actions ask first** > > Deleting a form, unpublishing it, canceling a request and a few other actions are marked for confirmation, and most AI tools ask you before running them. The prompt comes from your AI tool, so check its own approval settings. The full list is in the [MCP server reference](https://formstep.io/docs/developers/mcp-server/#confirmation). ## See or remove the connection In Formstep, open **OAuth and API Keys** in the workspace sidebar. Your connection is listed under **Connected apps**, with when it was connected and last used: ![The Connected apps card listing Claude Code (formstep), connected 1 minute ago and last used now, with a Disconnect button](https://formstep.io/docs/guides/ai-agents/connected-apps.png) Click the **Disconnect** button and confirm. The agent loses access at once, and asks you to sign in again the next time it needs Formstep. ## Next steps [Build a form with an AI agent](https://formstep.io/docs/guides/ai-agents/build-a-form/) — Describe the form, watch the preview, and publish it [MCP server](https://formstep.io/docs/developers/mcp-server/) — Every tool, limits, and how to connect with an API token instead # Build a form with an AI agent Ask Claude, ChatGPT, Cursor or another connected agent to build a Formstep form, watch it take shape in a preview, change it, and publish it. ## Before you start You need an AI tool with [Formstep connected](https://formstep.io/docs/guides/ai-agents/connect/). Any wording works for the prompts below, as long as it says what to ask and whether to publish. ## 1. Describe the form Name the form, list what it should ask, and say that it stays a draft for now: ``` Build a Formstep form called "[Guide] Supplier onboarding". Ask for the company name, a contact email, the company's VAT number, and whether they accept our 30-day payment terms (yes or no). Keep it as a draft and send me the preview link. ``` The agent creates the form in the workspace you connected, adds one question at a time, and reads the form back to check the order. It picks a question type for each item: an email field for the contact email, a single choice for yes or no. When it is done, it replies with the questions it added and a preview link. ## 2. Open the preview The preview link shows the draft as the recipient will see it, and it follows the agent’s edits live, so keep it open while you ask for changes. What the agent builds is an ordinary form: it is in Formstep under **Forms**, where you can open it in the editor and change anything by hand. ## 3. Ask for changes Say what to change in the same conversation, naming the questions by their titles: ``` Make the VAT number optional and add a file upload for their certificate of incorporation at the end. ``` The open preview shows the change as soon as the agent makes it: ![The preview of the Supplier onboarding draft after the change: a note that this is an unpublished version, then Company name, Contact email, VAT number, a Yes or No question about 30-day payment terms, a Certificate of incorporation upload, and a Submit button](https://formstep.io/docs/guides/ai-agents/form-preview.png) Most of what you set in the editor, you can ask an agent for: required or optional, a description, choice options, conditional logic, the theme, translations. For long forms, ask for one part at a time and check the preview in between. ## 4. Publish and get the field keys When the preview looks right, ask the agent to publish: ``` Looks good. Publish it, give me the link to share, and list each question's field key. ``` The agent publishes the form and lists the [field keys](https://formstep.io/docs/requests/field-keys/). The first publish always creates a [share link](https://formstep.io/docs/sharing-publishing/sharing-embedding/#share-links) anyone can open, and the agent gives it to you. An agent reads the keys from the published form, so asked before you publish, it says it cannot list them yet. Each key comes from the question’s title: _Company name_ becomes `company_name`. Keys are how a request prefills a question and how the answers come back, and they [stay the same](https://formstep.io/docs/requests/field-keys/#freeze) when you later rename a question. An agent cannot choose a key. To shorten a long one, edit it in the editor’s [Fields tab](https://formstep.io/docs/requests/field-keys/#where), best before the first publish, while no automation uses it yet. > **Publishing makes the form live** > > Once published, anyone with the share link can submit the form. To take answers only from named people, ask the agent to revoke the share link and [send requests](https://formstep.io/docs/guides/ai-agents/send-a-request/) instead. ## Next steps [Send a request with an AI agent](https://formstep.io/docs/guides/ai-agents/send-a-request/) — Ask one person to complete the form, prefilled, and read their answers back [Question types](https://formstep.io/docs/building-forms/field-types/) — Every question type an agent can add # Send a request with an AI agent Ask a connected agent to send a Formstep request to one person, prefill and lock what you already know, and read the answers back when they are done. ## Before you start You need an AI tool with [Formstep connected](https://formstep.io/docs/guides/ai-agents/connect/) and a published form. The examples use the one built in [Build a form with an AI agent](https://formstep.io/docs/guides/ai-agents/build-a-form/). What a request is, and every option it takes, is in [Creating requests](https://formstep.io/docs/requests/creating-requests/). ## 1. Ask for the request Say which form, who it goes to, what you already know, and how the link reaches them: ``` Send a Formstep request with the "[Guide] Supplier onboarding" form to Ada Lovelace (ada@acme.example). Fill in the company name "Analytical Engines Ltd" and lock it so she cannot change it. Use supplier-2041 as the external ID. Don't email her; give me the link and I will send it myself. ``` The agent finds the form, reads its [field keys](https://formstep.io/docs/requests/field-keys/), and creates the request. It replies with the link for the recipient and when the request expires. - A filled-in answer is prefilled; a locked one is shown but cannot be changed. See [Prefill, locked fields and hidden fields](https://formstep.io/docs/requests/creating-requests/#three-buckets). - The external ID is your own id for this piece of work, like a supplier or deal number. You can refer to the request by it later, also in a new conversation. - Ask the agent to email the recipient and Formstep sends the [invitation and reminders](https://formstep.io/docs/requests/invitations-and-reminders/), on Pro and Business. Otherwise you get the link and send it yourself. If the form has **Send reminders** on, Formstep still emails its scheduled reminders; to prevent that, also say _no reminders_. > **Try it with a test request first** > > Say _send a test request_ while you try this out. A [test request](https://formstep.io/docs/requests/creating-requests/#test-mode) never emails anyone and counts nowhere, so open the link yourself. The Requests page lists it only with the **Show test requests** filter on. To look it up by external ID, tell the agent it is a test request. A test request cannot be reminded. ## 2. The recipient completes the form The recipient opens the link and finds the company name filled in and locked: ![The request form with Company name showing Analytical Engines Ltd in a greyed-out field and the note 'Filled in by the sender.'](https://formstep.io/docs/guides/ai-agents/request-locked-field.png) The request shows in Formstep under **Requests**, pending until the recipient submits and completed after: ![The Requests list with Ada Lovelace, ada@acme.example, the Supplier onboarding form, and a Completed badge](https://formstep.io/docs/guides/ai-agents/requests-list.png) ## 3. Read the answers The agent is not told when the recipient submits. Ask it, in the same conversation or a new one: ``` Has the Formstep request supplier-2041 been answered? Show me the answers. ``` The agent looks the request up by its external ID and, once it is completed, lists each answer. A file answer comes with a download link. If the form has a [decision question](https://formstep.io/docs/requests/decisions-and-approvals/), the reply also says whether the recipient approved, declined or asked for changes. To act on answers without asking, have a workflow receive a [callback](https://formstep.io/docs/developers/callbacks/) when the request finishes, or start one from [Zapier](https://formstep.io/docs/guides/zapier/request-outcome/). ## Remind or cancel While a request is pending, ask the agent to _remind Ada about supplier-2041_ to send a reminder email now, or to _cancel_ it. Reminders need the recipient’s email and a Pro or Business plan; how often you can send them is in [Invitations, reminders and expiry](https://formstep.io/docs/requests/invitations-and-reminders/). Canceling is permanent: the link stops working at once, and most AI tools ask you to confirm first. ## Next steps [The Requests page](https://formstep.io/docs/requests/managing-requests/) — Filters, one request in detail, and what you can do with it [Field keys](https://formstep.io/docs/requests/field-keys/) — The short names an agent prefills and reads answers by [MCP server](https://formstep.io/docs/developers/mcp-server/) — Every request tool an agent can call, and the limits [Form templates](https://formstep.io/templates/) — Forms an agent can send as they are, each with its field keys listed --- ## Developers # Developers overview Call the Formstep REST API to send requests and manage forms, receive webhooks and request callbacks, or connect an AI agent over MCP. The endpoint, authentication, the request and response format, and rate limits. ## What you can do - Call the REST API to send a request to one person and read the answers, or to create, publish and change forms, share links, webhooks and translations. - Receive events: a webhook posts every submission of a form to your URL, and a request callback posts once, when a request ends. - Connect an AI agent over MCP, so Claude, ChatGPT, Cursor and other agents can do the same through tools. > **No code needed?** > > Zapier, Make and n8n connect Formstep click by click. See the [Automation guides](https://formstep.io/docs/guides/overview/). ## Pages in this section - [Send a request with the REST API](https://formstep.io/docs/developers/quickstart-send-a-request/) — Create an API token, read a form's field keys, and create a prefilled request with curl, from any tool that can make an HTTP call. - [Receive and verify the callback](https://formstep.io/docs/developers/quickstart-receive-the-callback/) — Run a small receiver in Node or Python, check each callback's signature with your signing secret, and read the answers Formstep pushes when a request ends. - [Create requests from code](https://formstep.io/docs/developers/requests-api/) — List a form's field keys, create a request with prefill, locked fields and context, hand the recipient documents, and read the answers. - [Create a form and a request in one call](https://formstep.io/docs/developers/inline-forms/) — Send the blocks with the request: Formstep builds the form, publishes it and returns the link, with no editor in between. - [Request callbacks](https://formstep.io/docs/developers/callbacks/) — Formstep POSTs a signed event to the callbackUrl of a request when it is completed, expires or is canceled. - [API reference](https://formstep.io/docs/developers/rest-api/) — Every REST API method with its parameters, examples and responses: forms, submissions, share links, requests, webhooks and more. - [API tokens](https://formstep.io/docs/developers/api-tokens/) — Create, manage, and rotate tokens for the API and MCP server. - [MCP server](https://formstep.io/docs/developers/mcp-server/) — Use Formstep from Claude, ChatGPT, Cursor and other MCP clients. - [Webhook API reference](https://formstep.io/docs/developers/webhooks-reference/) — Payload schema, event types, signing, retries, and REST subscription endpoints. ## Endpoint The REST API has one URL. Every call is a `POST` with a JSON body that names the method and its parameters. ```text POST https://api.formstep.io/api/v1 ``` ## Authentication Send an [API token](https://formstep.io/docs/developers/api-tokens/) as a bearer token in the `Authorization` header. A token reaches the one workspace it was created in. Create one from **OAuth and API Keys** in the workspace sidebar. **cURL** ```bash curl -X POST https://api.formstep.io/api/v1 \ -H "Authorization: Bearer fs_..." \ -H "Content-Type: application/json" \ -d '{"method": "forms.list", "params": {}}' ``` **JavaScript** ```ts const res = await fetch('https://api.formstep.io/api/v1', { method: 'POST', headers: { Authorization: `Bearer ${process.env.FORMSTEP_TOKEN}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ method: 'forms.list', params: {} }), }) const data = await res.json() ``` **Python** ```python import os, requests res = requests.post( "https://api.formstep.io/api/v1", headers={"Authorization": f"Bearer {os.environ['FORMSTEP_TOKEN']}"}, json={"method": "forms.list", "params": {}}, ) data = res.json() ``` > **Keep tokens on the server** > > Never put a token in browser code. Call the API from your backend. ## Request and response The body has two fields: `method` and `params`. Every method and its parameters are in the [API reference](https://formstep.io/docs/developers/rest-api/). ```json { "method": "forms.list", "params": { "workspaceId": "abc123..." } } ``` Every response has an `ok` field. On success, the result is in `data`. On failure, `error` holds a `code`, a `message`, and usually `details.reason`, which says what to fix. The codes and reasons are listed in the [error reference](https://formstep.io/docs/developers/rest-api/#error-reference). ```json { "ok": true, "data": { ... } } { "ok": false, "error": { "code": "VALIDATION_ERROR", "message": "Field 'name' is required." } } ``` ## Rate limits - 120 calls a minute per token, shared with the [MCP server](https://formstep.io/docs/developers/mcp-server/). - `requests.create` and `documents.create` also share a limit of 60 calls a minute per token. A call over a limit fails with `429 RATE_LIMITED` and `details.retryAfterMs`. Wait that long, then retry. # Send a request with the REST API Create an API token, read a form's field keys, and create a prefilled request with curl, from any tool that can make an HTTP call. You need a published form. This guide uses the one built in [Build a form with an AI agent](https://formstep.io/docs/guides/ai-agents/build-a-form/): company name, contact email, VAT number, a yes or no question about payment terms, and a file upload. Every method and option used here is described in the [API reference](https://formstep.io/docs/developers/rest-api/). ## 1. Create an API token In Formstep, open **OAuth and API Keys** in the workspace sidebar and [create a token](https://formstep.io/docs/developers/api-tokens/#create). The value is shown once. Keep it in an environment variable, not in your code: ```bash export FORMSTEP_TOKEN='fs_...' ``` A token reaches the one workspace it was created in. Every call below is a `POST` to the same URL with the token in the `Authorization` header; the body names the method and its parameters. ## 2. Find the form ID Open the form in the editor. The form ID is the part of the address after `/forms/`: ```text https://app.formstep.io//forms/jx75hdx8vb5hy1x85gm17nqgkn8f674g/edit ``` The commands below use this guide’s form ID. Put your own in its place. ## 3. List the field keys A request fills in questions, and the answers come back, by [field key](https://formstep.io/docs/developers/requests-api/#field-keys). Ask the form for its keys instead of guessing them from the titles: ```bash curl -X POST https://api.formstep.io/api/v1 \ -H "Authorization: Bearer $FORMSTEP_TOKEN" \ -H "Content-Type: application/json" \ -d '{"method": "fields.list", "params": {"formId": "jx75hdx8vb5hy1x85gm17nqgkn8f674g"}}' ``` The reply lists each question of the published form. Shortened: ```json { "ok": true, "data": { "published": true, "items": [ { "key": "company_name", "type": "text", "title": "Company name", "required": true, "prefillable": true }, { "key": "contact_email", "type": "email", "title": "Contact email", "required": true, "prefillable": true }, { "key": "vat_number", "type": "text", "title": "VAT number", "required": false, "prefillable": true }, { "key": "do_you_accept_our_30_day_payment_terms", "type": "radio", "title": "Do you accept our 30-day payment terms?", "required": true, "prefillable": true, "options": [{ "key": "yes", "label": "Yes" }, { "key": "no", "label": "No" }] }, { "key": "certificate_of_incorporation", "type": "file", "title": "Certificate of incorporation", "required": true, "prefillable": false } ], "hasMore": false } } ``` To prefill a choice question, send the option’s `key`, here `"yes"`, not its label. A question with `prefillable: false`, like the file upload, only the recipient can answer. The other flags are explained under [fields.list](https://formstep.io/docs/developers/rest-api/#fields-list). ## 4. Create a test request Send the request to Ada Lovelace, with the company name filled in and locked. `test: true` makes it a [test request](https://formstep.io/docs/developers/requests-api/#test-mode): it never emails anyone and counts nowhere, so you can repeat this step with a new `idempotencyKey` each time (on Free, up to 10 times a day). Its link closes within 24 hours. ```bash curl -X POST https://api.formstep.io/api/v1 \ -H "Authorization: Bearer $FORMSTEP_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "method": "requests.create", "params": { "formId": "jx75hdx8vb5hy1x85gm17nqgkn8f674g", "recipient": { "email": "ada@acme.example", "name": "Ada Lovelace" }, "prefill": { "company_name": "Analytical Engines Ltd" }, "readonly": ["company_name"], "externalId": "supplier-2043", "idempotencyKey": "supplier-2043", "test": true } }' ``` - `prefill` fills in answers the recipient can still change. A key in `readonly` is [locked](https://formstep.io/docs/requests/creating-requests/#three-buckets) as well. - `externalId` is your own id for this piece of work, like a supplier number. It comes back on every read and callback. - `idempotencyKey` makes a retry safe. If your code sends the same call twice, Formstep returns the first request with `deduplicated: true` instead of creating a second one. The reply carries the request ID and the link for the recipient: ```json { "ok": true, "data": { "id": "m17ayhcnj9xvzkff3atek49bdd8f6872", "status": "pending", "url": "https://form.formstep.io/r/rq_...", "deliveryStatus": "not_requested", "externalId": "supplier-2043", "deduplicated": false, "createdAt": 1790539225370, "expiresAt": 1793131225370 } } ``` `deliveryStatus: "not_requested"` means Formstep sent no email; you deliver the link yourself. To have Formstep send the invitation and reminders, add `"delivery": "email"`, which needs a Pro or Business plan; a Free account can try it on its own address. If the form has **Send reminders** on, a real request with a recipient email still gets the scheduled reminders; add `"reminders": []` to send none. See [Invitations and reminders](https://formstep.io/docs/developers/requests-api/#delivery). > **Test requests stay out of sight** > > The **Requests** page lists test requests only when you turn on the **Show test requests** filter. Open the `url` yourself to complete it. Drop `test` when you send the real thing. ## 5. Complete it and read the answers Open the `url` in a browser. The company name is filled in and locked; answer the rest and submit. Then read the request back with its ID: ```bash curl -X POST https://api.formstep.io/api/v1 \ -H "Authorization: Bearer $FORMSTEP_TOKEN" \ -H "Content-Type: application/json" \ -d '{"method": "requests.get", "params": {"requestId": "m17ayhcnj9xvzkff3atek49bdd8f6872"}}' ``` Once `status` is `completed`, the reply holds two maps keyed by field key. `answers` is for code: a choice answer is the option key, a file answer is a list of files with download URLs. `display` is for people: labels and file names. ```json "answers": { "company_name": "Analytical Engines Ltd", "do_you_accept_our_30_day_payment_terms": "yes", "certificate_of_incorporation": [{ "name": "certificate-of-incorporation.pdf", "type": "application/pdf", "size": 635, "url": "https://api.formstep.io/api/storage/..." }] }, "display": { "company_name": "Analytical Engines Ltd", "do_you_accept_our_30_day_payment_terms": "Yes", "certificate_of_incorporation": "certificate-of-incorporation.pdf" } ``` Asking again and again until the status changes is fine for a test. In production, give the request a `callbackUrl` and Formstep calls you when it ends; the next guide sets that up. ## Next steps [Receive and verify the callback](https://formstep.io/docs/developers/quickstart-receive-the-callback/) — Get the answers pushed to your endpoint, and check that Formstep sent them. [Create requests from code](https://formstep.io/docs/developers/requests-api/) — Every option a request takes: context, metadata, documents, expiry. # Receive and verify the callback Run a small receiver in Node or Python, check each callback's signature with your signing secret, and read the answers Formstep pushes when a request ends. You need an API token and a published form, as in [Send a request with the REST API](https://formstep.io/docs/developers/quickstart-send-a-request/), and Node or Python on your machine. What a callback carries, and how retries work, is in [Request callbacks](https://formstep.io/docs/developers/callbacks/). ## 1. Copy the signing secret Formstep signs every callback with your workspace’s request signing secret. Open **OAuth and API Keys** in the workspace sidebar and find the **Request signing secret** card: ![The Request signing secret card: Workspace secret, masked as rqs\_ followed by asterisks, with buttons to reveal, copy and regenerate it](https://formstep.io/docs/guides/rest-api/signing-secret.png) Click the copy button and keep the secret in an environment variable in the terminal where your receiver will run: ```bash export FORMSTEP_SIGNING_SECRET='rqs_...' ``` > **Do not regenerate it** > > The third button makes a new secret, and the old one stops working at once, even for callbacks already on their way. You do not need it to follow this guide. ## 2. Write the receiver Save the verify function from [Signing](https://formstep.io/docs/developers/webhooks-reference/#signing) next to your receiver: as `verify.mjs` for Node, since it uses `import`, or as `verify.py` for Python. The receiver reads the raw body, checks the signature, and only then parses the JSON: **Node.js** ```javascript import http from 'node:http' import { verifyFormstepSignature } from './verify.mjs' const secret = process.env.FORMSTEP_SIGNING_SECRET http .createServer((req, res) => { const chunks = [] req.on('data', (chunk) => chunks.push(chunk)) req.on('end', () => { const rawBody = Buffer.concat(chunks).toString('utf8') const signature = req.headers['x-formstep-signature'] ?? '' if (!verifyFormstepSignature(rawBody, signature, secret)) { console.log('rejected: bad signature') res.writeHead(401).end() return } const event = JSON.parse(rawBody) console.log(event.type, event.data.request.externalId, JSON.stringify(event.data.answers)) res.writeHead(200).end() }) }) .listen(8787) ``` **Python** ```python import json, os from http.server import BaseHTTPRequestHandler, HTTPServer from verify import verify_formstep_signature SECRET = os.environ['FORMSTEP_SIGNING_SECRET'] class Handler(BaseHTTPRequestHandler): def do_POST(self): raw_body = self.rfile.read(int(self.headers['Content-Length'])) signature = self.headers.get('X-Formstep-Signature', '') if not verify_formstep_signature(raw_body, signature, SECRET): print('rejected: bad signature', flush=True) self.send_response(401) self.end_headers() return event = json.loads(raw_body) print(event['type'], event['data']['request'].get('externalId'), event['data'].get('answers'), flush=True) self.send_response(200) self.end_headers() HTTPServer(('', 8787), Handler).serve_forever() ``` Hash the body exactly as it arrived. Parsing it and turning it back into JSON changes the bytes, and the signature no longer matches. ## 3. Run it and give it a public URL Start the receiver: ```bash node receiver.mjs # or: python3 receiver.py ``` Formstep only calls public HTTPS addresses, so `localhost` will not do. While you test, a tunnel gives your machine one. [Cloudflare’s quick tunnel](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/do-more-with-tunnels/trycloudflare/) needs no account. Run it in a second terminal: ```bash cloudflared tunnel --url http://localhost:8787 ``` It prints an address like `https://horn-cod-classics-arab.trycloudflare.com`. ngrok and similar tools work the same way. In production, use your server’s own HTTPS URL instead. ## 4. Send a request with a callback URL Create a test request as in the [previous quickstart](https://formstep.io/docs/developers/quickstart-send-a-request/#create), and add `callbackUrl` with the tunnel address. Use your own form ID: ```bash curl -X POST https://api.formstep.io/api/v1 \ -H "Authorization: Bearer $FORMSTEP_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "method": "requests.create", "params": { "formId": "jx75hdx8vb5hy1x85gm17nqgkn8f674g", "recipient": { "email": "ada@acme.example", "name": "Ada Lovelace" }, "prefill": { "company_name": "Analytical Engines Ltd" }, "readonly": ["company_name"], "externalId": "supplier-2044", "idempotencyKey": "supplier-2044", "callbackUrl": "https://horn-cod-classics-arab.trycloudflare.com/formstep", "test": true } }' ``` Use a new `idempotencyKey`: the same key with a different body is rejected. Open the `url` from the reply, answer the form, and submit. ## 5. See the callback arrive A few seconds after you submit, the receiver prints the event type, your external ID and the answers. The Node receiver prints: ```text request.completed supplier-2044 {"certificate_of_incorporation":[{"name":"certificate-of-incorporation.pdf","size":635,"type":"application/pdf","url":"https://api.formstep.io/api/storage/...",...}],"company_name":"Analytical Engines Ltd","contact_email":"ada@acme.example","do_you_accept_our_30_day_payment_terms":"yes","vat_number":"GB123456789"} ``` To see a rejection, send the receiver any POST yourself, for example `curl -X POST -d '{}' http://localhost:8787`. It has no valid signature, so the receiver prints `rejected: bad signature` and answers 401. ## Before you go live - Answer with a 2xx within 10 seconds. A 5xx, a 408, a 429 or a timeout is retried for about four hours, and any other 4xx, like the 401 above, stops the retries. See [Retries](https://formstep.io/docs/developers/callbacks/#retries). - Branch on `type`. The same URL also hears `request.expired` and `request.canceled`, which carry no answers. - Handle each event once. A retry carries the same `id`, so store the ids you have handled and skip a repeat. - Check `test`. A callback from a test request has `"test": true`. Drop `test` from `requests.create` for real recipients. If your receiver was down for longer than the retries last, the answers are not lost. Read them with `requests.get`, or send the callback again with [requests.replayCallback](https://formstep.io/docs/developers/rest-api/#requests-replay-callback) or from the [Requests page](https://formstep.io/docs/requests/managing-requests/). ## Next steps [Request callbacks](https://formstep.io/docs/developers/callbacks/) — The full payload, the three events, and the retry schedule. [API reference](https://formstep.io/docs/developers/rest-api/) — Every method, with its parameters and responses. # Create requests from code List a form's field keys, create a request with prefill, locked fields and context, hand the recipient documents, and read the answers. The value shapes, idempotency, test requests and the limits that apply. To try it end to end with curl first, follow [Send a request with the REST API](https://formstep.io/docs/developers/quickstart-send-a-request/). Every parameter is listed under [Requests](https://formstep.io/docs/developers/rest-api/#requests) in the API reference. What a request is, and what the recipient sees, is explained in [Requests](https://formstep.io/docs/requests/overview/). ## Copy a call from the New request sheet ![From an automation in the New request sheet, showing curl and MCP prompt, the form id and a ready-made requests.create call](https://formstep.io/docs/images/requests/request-automation.png) From an automation: the form id and a call already filled in with this form's field keys and what you filled in. Open a published form, click **New request** (the paper plane icon), and fill in the sheet as if you were sending it by hand. Then open the **⋯** menu at the bottom of the sheet and pick **From an automation**. It shows the form id and the same request as a curl call or an MCP prompt, with this form's real field keys. **OAuth and API Keys** in the same menu opens the page where you create the [API token](https://formstep.io/docs/developers/api-tokens/). Only a published form that is open for submissions takes requests. A form that was never published fails with `FORM_NOT_PUBLISHED`, a closed one with `FORM_CLOSED`. ## List the fields [fields.list](https://formstep.io/docs/developers/rest-api/#fields-list) returns every field of the form's current published version, with its key, its type and a few flags. Read the keys from it instead of guessing them from question titles. The flags tell you where a value goes: | The field shows | What you send | | - | - | | prefillable: true | A value in prefill, in the shape its type asks for (see value shapes below). | | options, or rows and columns | The option key, not the label. The label is there so you can match the choice you know to its key. | | context: true | A hidden field. Its value goes in context, never in prefill; in prefill it fails with UNKNOWN\_FIELD\_KEY. | | calculated: true | Nothing. The form works the value out, and you read it back under its key in answers. | | prefillable: false (and no other flag) | Nothing. File upload, signature, payment, appointment booking and Documents blocks are for the recipient. | | type: "group", repeating: true | A repeating group. Its members are listed under members, and you send a list of items. | ### Field keys A field key is the name your code uses for one field, such as `company_name`. Authors see and edit keys in the editor's **Fields** tab; see [Field keys](https://formstep.io/docs/requests/field-keys/). Until someone edits it, a key comes from the question title: lowercase, accents removed, every space or symbol turned into an underscore, up to 64 characters. | Question title | Key | | - | - | | Company name | company\_name | | What is your VAT number? | what\_is\_your\_vat\_number | | Prénom | prenom | | Søknad om støtte | soknad\_om\_stotte | | الاسم الكامل | field\_3: no Latin letters or digits, so the key is field\_ plus its position | - When two fields would get the same key, the later one gets `_2`, `_3` and so on. - A key set by hand uses letters, digits, `_`, `.` and `-`, up to 64 characters. Keys are case-sensitive and unique within the form. - From the first publish, a field keeps its key when the question is renamed, moved or given another type. A new type may need a new value shape. A duplicated question gets a new key. - A key disappears when someone types a new one, or deletes a question and adds another in its place. Callbacks then arrive without that answer, and `requests.create` refuses the old key with `UNKNOWN_FIELD_KEY`. Publishing warns first, and the MCP tool `form_publish` returns the same messages in `warnings`. - Publishing stops on a clash: two fields with one key (questions, repeating groups, hidden fields and calculated fields share one set of keys), a key already published on another field, two options of one question with one key, or a hidden field whose parameter name is invalid or used twice. A dropped field key or option key is only ever a warning. The same checks run when the API or an agent publishes. - Each option of a choice question, and each row and column of a matrix, has an option key, derived from its label the same way and unique within its question. A workflow can branch on `answers.plan == "pro"` whatever language the recipient used. A repeating group has its own key, and so does each field inside it. The members are nested, never top-level: ```json { "contacts": [{ "name": "Ada", "email": "ada@acme.com" }, { "name": "Grace", "email": "grace@acme.com" }] } ``` A hidden field's key is also its URL parameter on a share link. A calculated field's name is its key, so its value comes back as, for example, `answers.total`. ## Create the request ```json { "method": "requests.create", "params": { "formId": "j57...", "recipient": { "email": "ada@acme.com", "name": "Ada" }, "prefill": { "company_name": "Acme", "company_size": "51_200" }, "readonly": ["company_name"], "context": { "case_id": "CASE-9" }, "metadata": { "crm_record": "rec_88" }, "delivery": "email", "externalId": "run-42", "callbackUrl": "https://automation.example/webhook/resume-abc", "idempotencyKey": "run-42" } } ``` The reply carries the request `id`, its `status` (`pending`), the recipient's `url`, `deliveryStatus` and `expiresAt`; the full response is under [requests.create](https://formstep.io/docs/developers/rest-api/#requests-create). Store the `url` if you deliver the link yourself. A request carries four kinds of values. What each one means for the recipient is explained in [Prefill, locked fields and context](https://formstep.io/docs/requests/creating-requests/#three-buckets). The API rules: | Parameter | Takes | Rules | | - | - | - | | prefill | Answers by field key, for visible questions | The recipient can change them. Each value must fit its type. | | readonly | A list of prefilled keys to lock | Every key must also be in prefill (READONLY\_REQUIRES\_PREFILL), and a locked required field needs a non-empty value (READONLY\_REQUIRED\_EMPTY). | | context | Values for the form's hidden fields | Each key must be a hidden field on the published form (UNKNOWN\_FIELD\_KEY; a visible question gives CONTEXT\_KEY\_NOT\_HIDDEN\_FIELD). A value is a string, number or boolean. Request links ignore URL parameters, so the recipient cannot change it. | | metadata | Any object of your own | Never reaches the form. Comes back in every callback and read. | ## Value shapes Send each value in the shape its `type` asks for. A wrong shape fails with `INVALID_PREFILL_VALUE`, and the error names the key, the expected type and, for a choice question, the accepted option keys. | Type | Send | | - | - | | text, email, phone, url, textarea | A string | | number, rating, scale | A number | | switch | true or false | | date | "2026-03-04" | | time | "09:30" or "09:30:00" | | radio, select | The option key, not its label | | checkbox, ranking, picture-choice | A list of option keys | | matrix | An object of row key to column key: { "row\_key": "column\_key" } | | group (repeating) | A list of at most 100 items: \[{ "member\_key": value }, ...] | | file, signature, payment, schedule-appointment | Nothing; the recipient supplies these | | any field with calculated: true | Nothing; the form works it out | | documents | Nothing in prefill; use the documents parameter below | ## Documents for one recipient A [Documents block](https://formstep.io/docs/building-forms/documents-block/) shows files to the recipient. A request can add files for its one recipient, such as their own contract, below the files every recipient gets. You upload the file to a URL Formstep gives you, so the bytes do not pass through the API: 1. **Reserve the upload** Call [documents.create](https://formstep.io/docs/developers/rest-api/#documents-create) with the formId, a name, the contentType, the exact size in bytes and, if you like, a sha256. You get back an id and an uploadUrl that is valid for one hour. 2. **Upload the bytes** PUT the file to uploadUrl with the same Content-Type. 3. **Reference it on the request** Pass documents: \[{ documentId, field?, name? }] on requests.create. Formstep checks the uploaded file before the request is created. ```json "documents": [ { "documentId": "kn7...", "name": "Your lease contract" }, { "documentId": "kn8..." } ] ``` `name` replaces the display name for this request. Set `field` to the block's field key when the form has more than one Documents block. One upload can be used by any number of requests. The accepted types and the size caps are listed under [documents.create](https://formstep.io/docs/developers/rest-api/#documents-create). The callback lists the files this recipient was given, under the block's field key. ## Invitations and reminders `delivery: "email"` has Formstep email the invitation; it needs `recipient.email`. The default, `"none"`, leaves the link to you. Invitations and reminders need Pro or Business. On Free, an invitation uses one of the [free invitations](https://formstep.io/docs/subscription-billing/limits-quotas/#free-invitations) and goes only to your own address: when they are used up the call fails with `FREE_INVITATIONS_USED`, and another address fails with `FREE_INVITATIONS_OWN_ADDRESS_ONLY`. Your own address is the one you sign in with, or the one your API token or MCP connection belongs to, and spellings of one address count as one: a `+tag` or dots in a Gmail address do not make a new one. A deduplicated call and a test request spend no free invitation. The email itself, and how the reminder schedule runs, are explained in [Invitations, reminders and expiry](https://formstep.io/docs/requests/invitations-and-reminders/). ### Delivery status | deliveryStatus | Meaning | | - | - | | not\_requested | Created with delivery "none". You deliver the link. | | queued | Accepted, not sent yet. | | sent | Handed to the email provider. | | failed | Formstep could not send it, usually a plan or address problem. Fix it, then send a reminder: it carries the same link. | | bounced | The provider reported a hard bounce or a complaint. Create a new request for the right address; a reminder will not help. | ### Email limits - One recipient address gets at most 10 emails a day from one form, invitations and reminders together. Over it, the call fails with `RATE_LIMITED` and reason `RECIPIENT_EMAIL_LIMITED`. - An account sends at most 30 invitations and reminders an hour in its first 7 days, then 200 an hour. Over it, the call fails with reason `INVITATION_RATE_LIMITED`; retry after `retryAfterMs`. A scheduled reminder over it goes out later. - A request emails its recipient at most nine times: the invitation and up to eight reminders, manual and scheduled together. [requests.remind](https://formstep.io/docs/developers/rest-api/#requests-remind) sends one now, at least 10 minutes after the last. ### The reminders parameter Without `reminders`, a request with a recipient email follows the form's schedule when **Send reminders** is on. `reminders` replaces that schedule for one request, and applies even when the setting is off: - Up to five offsets of idle time, in whole days, hours or minutes, each at least one hour: `["2d", "12h", "90m"]`. - `[]` sends no reminders. - A schedule of your own needs a recipient email and Pro or Business. A duplicate offset, an offset under one hour or a sixth step fails with `INVALID_REMINDER_SCHEDULE`. ## Other options | Option | What it does | | - | - | | language | The language the form opens in and the invitation is written in: one of the form's published languages, or LANGUAGE\_NOT\_PUBLISHED. The recipient can still switch. | | expiresAt | When the link stops working, in epoch milliseconds. Default 30 days out, at most 365 (EXPIRY\_OUT\_OF\_RANGE). | | externalId | Your own id for the request. Comes back on every read and callback, and filters requests.list. | | callbackUrl | Where Formstep POSTs the event when the request ends. See [Request callbacks](https://formstep.io/docs/developers/callbacks/). | | domainId | The custom domain the link is minted on. Without it, the link uses the domain the form already has a share link on. Needed when there are several. | | form | Build the form in the same call instead of sending formId. See [Inline forms](https://formstep.io/docs/developers/inline-forms/). | ### Test requests `test: true` runs the whole flow without a real recipient. The call is checked like a real one, the link opens and can be completed, the callback fires with `"test": true`, and `requests.get` returns the answers. A test request: - sends no invitation and no reminders, and costs nothing from the monthly allowance; - keeps its answers out of the inbox, submission counts, exports, integrations, subscriptions and notifications; - is hidden on the Requests page until you turn on **Show test requests**, and left out of `requests.list` unless you pass `includeTest: true`; - closes within 24 hours, whatever `expiresAt` says. On Free, a workspace can create 10 test requests a day. The next fails with `RATE_LIMITED` and reason `TEST_REQUEST_LIMIT_REACHED`. ### Idempotency Send the same `idempotencyKey` with the same body, and you get the original request back with `deduplicated: true`: no second request and no second email. The same key with a different body fails with `IDEMPOTENCY_CONFLICT`. Keys apply per workspace for 30 days. In a workflow tool, use the execution id as the key, so a retried run finds the request it already created. ### What a request costs Each request spends one unit of the workspace's [monthly allowance](https://formstep.io/docs/subscription-billing/limits-quotas/) when it is created, answered or not. When the allowance is spent, `requests.create` fails with `UPGRADE_REQUIRED` and reason `MONTHLY_ALLOWANCE_REACHED`. Requests already created can still be answered. ### Rate limit `requests.create` and `documents.create` share a limit of 60 calls a minute per token, on top of the [general limit](https://formstep.io/docs/developers/overview/#rate-limits). A call over it fails with `RATE_LIMITED` and can be retried after `retryAfterMs`. ## Read the answers Wait for the [callback](https://formstep.io/docs/developers/callbacks/), or read the request with [requests.get](https://formstep.io/docs/developers/rest-api/#requests-get). Once `status` is `completed`, it holds two maps keyed by field key: `answers` for code (option keys, numbers, file lists with download URLs) and `display` for people (labels, file names). A request is deleted once the form's retention window passes after it ends, so read what you need when it ends. ### Outcome `status` says whether the request finished; `outcome` says what the recipient decided. A [decision question](https://formstep.io/docs/requests/decisions-and-approvals/) is a radio keyed `decision` whose options have the keys `approve`, `decline` and `changes`. When the recipient picks one of the three, the request completes with that outcome: ```json "request": { "id": "kd7...", "externalId": "run-42", "status": "completed", "outcome": "changes" }, "answers": { "decision": "changes", "reason": "Please split the line items per site." } ``` - The outcome is on `data.request.outcome` in the callback and next to `status` on `requests.get`. The verdict is also an ordinary answer under `decision`. - `requests.list` takes an `outcome` filter, which implies completed requests only. - There is no outcome when the recipient picks a fourth option, when the option keys were changed (publishing warns about that), when the decision question sits inside a repeating group, or when the request expired or was canceled. ## From an AI agent Over the [MCP server](https://formstep.io/docs/developers/mcp-server/), the same calls are the tools `fields_list`, `request_create`, `request_get`, `request_list`, `request_cancel`, `request_remind` , `request_replayCallback` and `document_create`, with the same parameters. - `fields_list` adds a `usage` line to each key that names the argument it belongs in. Read it again after `form_publish`. - Every request tool's result has a `next` line telling the agent what to do from there. - The ids for `domainId` come from `formShareLink_list`, as `availableCustomDomains`. - `editor_insertDecisionQuestion` adds a decision question; it takes a title and the three labels in the form's language. - An agent with nobody to set up a receiver leaves out `callbackUrl` and polls `request_get` every few minutes until `status` leaves `pending`, then reads `outcome`. See [Callbacks or polling](https://formstep.io/docs/developers/mcp-server/#requests-callbacks). - A `request_list` page can come back with no items and `hasMore: true`. The list goes on, so pass the cursor back and keep going. [Request callbacks](https://formstep.io/docs/developers/callbacks/) — What arrives when the request ends, and how to verify it. [Error reference](https://formstep.io/docs/developers/rest-api/#error-reference) — Every error code and reason, and what to do. # Create a form and a request in one call Send the blocks with the request: Formstep builds the form, publishes it and returns the link, with no editor in between. ## When to use it A request usually points at a form you built in the editor. When the questions depend on the case, for example an agent on the phone learns that a recipient still has to upload two payslips and pay a fee, send `form` instead of `formId`. Formstep builds an ordinary form from the blocks, publishes it, and creates the request. Either both are created or neither is. This is an **inline form**. Use `formId` for a form you send to many people in the same shape. Use `form` when a workflow decides what to ask. ## The call ```bash curl -X POST https://api.formstep.io/api/v1 \ -H "Authorization: Bearer $FORMSTEP_TOKEN" \ -H "Content-Type: application/json" \ -d '{"method":"requests.create","params":{ "form":{ "name":"Documents and fee for your mortgage application", "logo":{"type":"image","url":"https://acme.example/logo.png"}, "cover":{"type":"color","color":"#0f4c81"}, "emoji":"🏦", "blocks":[ {"type":"paragraph","content":"Upload the two documents and pay the processing fee."}, {"type":"file","key":"payslips","title":"Your last two payslips","maxFiles":2}, {"type":"payment","key":"fee","title":"Processing fee","amount":40,"currency":"EUR"}, {"type":"scheduleAppointment","key":"call","title":"Pick a time for our call","eventType":"15 min call"}]}, "recipient":{"email":"ada@example.com","name":"Ada"}, "delivery":"email", "idempotencyKey":"call-8841"}}' ``` All other [request options](https://formstep.io/docs/developers/requests-api/) work as usual. The response adds the form and the keys its answers come back under: ```json { "id": "kd7...", "status": "pending", "url": "https://form.formstep.io/r/rq_...", "form": { "id": "j58...", "name": "Documents and fee for your mortgage application", "reused": false, "fields": [ { "key": "payslips", "type": "file" }, { "key": "fee", "type": "payment" }, { "key": "call", "type": "schedule-appointment" } ] } } ``` To prefill a choice question in the same call, send option keys, as for any form. An option’s key is derived from its label, so `Self-employed` becomes `self_employed`. A label instead of a key fails with `INVALID_PREFILL_VALUE`, which lists the valid keys. ## The form object | Field | What it does | | - | - | | name | Required, 1 to 100 characters. The name on the dashboard and in the browser tab, the same for every recipient. Personalise through prefill and context, never through the name. | | blocks | Required, 1 to 200 blocks, in the order the form shows them. The blocks in a row's columns and a repeating group's fields count too. | | language | The form's UI language. Default: English. | | logo, cover, emoji | Your brand, sent with every request. logo: { "type": "icon", "name": "HeartIcon" } or { "type": "image", "url": "https\://…" }. cover: { "type": "color", "color": "#0f4c81" } or { "type": "image", "url": "https\://…", "offsetY": 50 }. emoji: one emoji beside the name. See below. | | theme | A look per color mode: { "light": "solarized-light", "dark": "dracula" }. Each mode takes a preset id, free on every plan, or a whole theme object as the MCP tool formTheme\_set takes it, which needs the Pro plan. | | settings | The form behaviour the settings page sets: branding, a verified email domain (emailDomainId), owner and respondent emails with their copy, the reminder schedule, the invitation email, the redirect and retention. Fields by key: respondentNotificationTo is the key of an email question, redirectQueryParams maps a parameter to a field key. Email copy is plain text with {{key}} placeholders. No password or sign-in gate and no share-link setting such as bot protection or edits after submit: a request carries its own link. Any other key is refused. | | translations | The form in more languages, with a language switcher for the recipient: { "de": { "Your name": "Ihr Name" } }, every string of the form keyed by the text as the blocks wrote it. See below. | ## Blocks A block takes the parameters of the matching `editor_insert*` tool on the [MCP server](https://formstep.io/docs/developers/mcp-server/), without `formId` and `after`, plus an optional `key`. Without one, the key is derived from the title, so “Full name” becomes `full_name`. A parameter the block does not know is refused rather than dropped. | Kind | Types | | - | - | | Questions | text, contact, number, date, time, radio, checkbox, select, switch, rating, linearScale, matrix, ranking, pictureChoice, file, signature, decision | | Documents | documents, to show files the recipient reads or signs | | Payments and bookings | payment, scheduleAppointment | | Content | header, paragraph, image (src URL), embedded, table (cells fills it), list, row (columns: the blocks of each column, 2 to 6 columns, and widths in percent). An optional key lets logic show or hide the block. | | Structure | pageDivider, repeatingGroup (fields: the member questions as blocks, each with its key), hiddenField (key required: it is the context key), calculatedField (its name is its key), logic | | AI Validation | aiValidation (sentence: the rule, naming the answers it reads as {{key}}, the questions above it and hidden fields; key: optional, the key its result gets in data.aiValidations) | Header and paragraph text, list items and table cells read `**bold**`, `*italic*`, `~~struck~~`, `underlined`, `` `code` ``, `$$equation$$`, `[text](https://url)`, `…` with the editor toolbar’s colors, fonts and sizes, and `{{key}}`, which shows the value of the field with that key. Marks nest; an unmatched marker is text. A column of a row holds any block except pageDivider, logic, hiddenField, calculatedField, aiValidation, repeatingGroup, row and scheduleAppointment. A `decision` block takes its `title` and `labels` for approve, decline and changes in the form’s language. An `aiValidation` block checks the answers its sentence names against it with AI while the recipient fills in the form, and shows a note when they do not fit; the recipient can still submit. For example, `{ "type": "aiValidation", "key": "invoice_check", "sentence": "{{invoice}} is an invoice for order {{order_number}}, dated in 2026." }` right after an `invoice` file question, with `order_number` a hidden field the request’s context fills. Its result arrives as `data.aiValidations.invoice_check`. See [AI Validation](https://formstep.io/docs/building-forms/ai-validation/). Blocks refer to each other by key, never by element id: a `logic` block takes its `rule` as `editor_insertLogic` does, with field keys where it wants ids, option labels as condition values and page names for `setNextPage`. ### Payments A `payment` block takes `amount` and `currency`, or `amountFromField`: the key of a hidden field or a number question. With a hidden field, each request passes its own amount in `context`, so one form charges every recipient their own amount: ```json { "form": { "name": "Pay your invoice", "blocks": [ { "type": "hiddenField", "key": "invoice_total" }, { "type": "payment", "key": "invoice", "title": "Invoice", "currency": "EUR", "amountFromField": "invoice_total" } ] }, "recipient": { "email": "ada@example.com" }, "context": { "invoice_total": 1200 } } ``` A number question that sets the amount must be required and have a `min` above 0, so nobody reaches checkout with an empty, zero or negative amount. The form charges through the workspace’s default Stripe account, or the one `connection` names. A person [connects Stripe once for the workspace](https://formstep.io/docs/building-forms/payment-collection/#connect-stripe); an automation cannot. Without a usable account the call fails and tells you where to connect it. ### Bookings A `scheduleAppointment` block takes `eventType`, the title or id of an event type on the workspace’s [Cal.com](https://formstep.io/docs/integrations/cal-com/) connection, and `connection` when the workspace has several. Formstep looks the event type up when the request is created. `prefill: { name, email }` names the questions whose answers fill in the booking. ## Your brand An automation has no way to know your look, so you set it up once and the automation sends it with every request: `logo`, `cover` and `emoji` beside the blocks, and `theme` for the colors and type. | Field | Takes | | - | - | | logo | An icon from the builder, { "type": "icon", "name": "HeartIcon" }, or your own image, { "type": "image", "url": "https\://…/logo.png" }. | | cover | A solid color, { "type": "color", "color": "#0f4c81" }, or an image, { "type": "image", "url": "https\://…/cover.jpg", "offsetY": 50 }, where offsetY from 0 to 100 picks the part of a tall image that shows. | | emoji | One emoji, shown beside the form name. | Every image, the `src` of an image block included, must be a public JPEG, PNG or WebP. Formstep fetches each once when the form is created and keeps its own copy, so the form and the PDFs of its submissions outlive the source. A URL that cannot be fetched fails the call with `INLINE_FORM_IMAGE_UNREACHABLE` naming the field. Without a brand, the form looks like a new form from the dashboard. ## Translations `translations` publishes the form in more languages at once, and the recipient gets the language switcher a translated form has: `{ "de": { "Your name": "Ihr Name", "Order for **{{customer}}**": "Bestellung für **{{customer}}**" } }`. Every string of the form must be there, keyed by the text exactly as the blocks wrote it: question titles, options, placeholders, headers, paragraphs, list items, table cells, group names and button labels. Markup and `{{key}}` stay as in the source. A string missing fails the call with `INLINE_FORM_TRANSLATION_MISSING`, which lists the strings to add. Email copy is not translated. ## Repeat calls reuse the form A later call with the same `form` object reuses the same form, and the response says `"reused": true`. A hundred requests with one definition make one form, with one set of answers to export. Changing a block, the name, the brand or a translation makes a new form; requests already sent keep the form they were created with. A form you close for submissions or unpublish is not reused: the next call with the same definition makes a new one. > **Personalise through prefill and context** > > Put the recipient’s name, the invoice number or the amount in `prefill` and `context`, never in the form name, a title or a fixed amount. A definition that changes per recipient makes a new form per recipient. Set `key` on every question: a derived key follows the title, so a retitled question answers under a new key. A workspace makes at most 300 new inline forms a day, and calls that reuse a form do not count. When a call makes a new form although an open inline form has the same block types in the same order, the response names that form in `form.similarForm`. Usually a changed name or title caused it. To read every answer to one inline form, list its submissions instead of reading each request. ## On the dashboard An inline form belongs to its request, so it lives on the request’s page rather than on the Forms page. The page lists the answers by field key, and **Preview** opens the form as the recipient saw it, filled in with their answers. A link to an inline form’s editor or submissions opens the Requests page filtered to that form instead. Agents still see inline forms in `form_list`, and the API refuses to change one. An inline form has no share link, and none can be added: its requests are the only way in, and the callback carries every answer. It has no integrations either. Because it has no page of its own, an inline form is never deleted by hand. It goes with its last request: when you [delete a request](https://formstep.io/docs/requests/managing-requests/#delete), or the form’s retention window takes it, and no other request still uses the form, the form is deleted too. ## When the call is refused Each refusal carries a `details.reason` your automation can branch on, and nothing is created. All of them are `VALIDATION_ERROR` except the daily limit, which is `RATE_LIMITED`. | Reason | What to do | | - | - | | FORM\_SOURCE\_MISSING / FORM\_SOURCE\_AMBIGUOUS | Send exactly one of formId and form. | | INLINE\_FORM\_INVALID | A block or a field of form fails its schema. details.issues names each one. | | INLINE\_FORM\_DUPLICATE\_KEY | Two blocks resolve to the same key. Give one of them its own key. | | INLINE\_FORM\_UNKNOWN\_KEY | A logic rule, amountFromField or a booking prefill names a key no block has. | | INLINE\_FORM\_AMOUNT\_MISSING | Pass a positive number for the amount field in context. | | INLINE\_FORM\_DAILY\_LIMIT | The workspace made 300 new inline forms today. This one comes back as RATE\_LIMITED with retryAfterMs; calls that reuse a form keep working. | | INLINE\_FORM\_CONNECTION\_MISSING | Stripe or Cal.com is not connected. details.connectUrl is the page a person opens once to connect it. | | INLINE\_FORM\_EVENT\_TYPE\_NOT\_FOUND | No Cal.com event type matches. The error lists the ones there are. | | INLINE\_FORM\_IMAGE\_UNREACHABLE | An image URL is no public JPEG, PNG or WebP. details.field names it. Use a URL anyone can open, or drop the image. | | INLINE\_FORM\_TRANSLATION\_MISSING | A translation lacks strings the form has. details.missing lists them, as written in the blocks. | | INLINE\_FORM\_PUBLISH\_ISSUES | Publishing refused the form, for instance an event type that cannot be booked from a form. | ## From an agent On the MCP server, `request_create` takes the same `form`. Before building a form with a payment or a booking, an agent reads `workspaceConnection_list`: it shows the connected Stripe and Cal.com accounts and, when one is missing, the link a person must open once. `scheduler_listEventTypes` lists the event types a booking can name, and `paymentConnection_setDefault` changes the default Stripe account. Over the REST API the same three are `connections.list`, `scheduler.listEventTypes` and `paymentConnections.setDefault`. [Create requests from code](https://formstep.io/docs/developers/requests-api/) — Prefill, locked fields and context for any form. [Field keys](https://formstep.io/docs/developers/requests-api/#field-keys) — How keys are derived and kept stable. [Request callbacks](https://formstep.io/docs/developers/callbacks/) — Receive the answers when the request ends. [Payment collection](https://formstep.io/docs/building-forms/payment-collection/) — Connect Stripe for the workspace. # Request callbacks Formstep POSTs a signed event to the callbackUrl of a request when it is completed, expires or is canceled. Read the payload, verify the signature, and handle retries and replays. Set `callbackUrl` on [requests.create](https://formstep.io/docs/developers/rest-api/#requests-create). It must be HTTPS and resolve to a public address, so `localhost` does not work; while you test, use a tunnel as in [Receive and verify the callback](https://formstep.io/docs/developers/quickstart-receive-the-callback/). ## What fires, and when | Event | When | | - | - | | request.completed | The recipient submitted. Carries the answers. | | request.expired | The expiry passed while the request was still pending. | | request.canceled | You or your automation withdrew it. | All three go to the same URL, so check `type` before you read answers. An expiry reaches you within 15 minutes of `expiresAt`. ## What arrives ```json { "id": "evt_kj7...", "type": "request.completed", "createdAt": "2026-03-04T09:31:40.000Z", "apiVersion": "2026-09-24", "test": false, "data": { "request": { "id": "kd7...", "externalId": "run-42", "status": "completed", "outcome": "approve", "language": "en", "recipient": { "email": "ada@acme.com", "name": "Ada" }, "metadata": { "runId": "run-42" }, "context": { "case_id": "CASE-9" }, "createdAt": "2026-03-04T09:20:00.000Z", "completedAt": "2026-03-04T09:31:40.000Z" }, "form": { "id": "j57...", "name": "Vendor onboarding", "snapshotId": "kx2..." }, "submission": { "id": "jd7...", "respondentEmail": "ada@acme.com", "submittedAt": "2026-03-04T09:31:40.000Z", "updatedAt": null, "editCount": 0, "pdfUrl": null, "language": "en" }, "answers": { "company_name": "Acme", "contacts": [{ "name": "Ada" }] }, "display": { "company_name": "Acme", "contacts": "Ada" }, "aiValidations": { "company_check": { "status": "passed", "fields": ["company_name"] } } } } ``` - `data.request` is always there, with your `externalId`, `metadata` and `context` unchanged. It carries the timestamp of whichever ending happened: `completedAt`, `expiredAt`, or `canceledAt` with an optional `cancelReason`. - `outcome` is present only when the recipient answered a [decision question](https://formstep.io/docs/developers/requests-api/#outcome): `approve`, `decline` or `changes`. Expired and canceled requests have none. - `form`, `submission`, `answers` and `display` appear on completion only, in the shape a [submission webhook](https://formstep.io/docs/developers/webhooks-reference/#payload) carries. `form.snapshotId` is the published version the recipient answered. `submission.pdfUrl` is a URL only when the form keeps a submission PDF. - `answers` is keyed by [field key](https://formstep.io/docs/developers/requests-api/#field-keys), with repeating groups nested as one object per item. A choice answer is the option key; its label is in `display` under the same key. - `aiValidations` is there when the form has [AI Validations](https://formstep.io/docs/building-forms/ai-validation/). Its fields are described on the [webhooks reference](https://formstep.io/docs/developers/webhooks-reference/#payload). - `test` is `true` for a [test request](https://formstep.io/docs/developers/requests-api/#test-mode). Branch on it, or drop the event. [requests.sample](https://formstep.io/docs/developers/rest-api/#requests-sample) returns this envelope for any form without a real request, which helps when you map fields before the first one ends. > **Deduplicate on id** > > `id` stays the same across every retry and every replay of the same event. If acting twice would hurt, such as a second invoice or a second ticket, store the ids you have handled and skip a repeat. ## Headers - `Content-Type: application/json` and `User-Agent: Formstep` - `X-Formstep-Event-Id` and `X-Formstep-Event-Type`, the same values as `id` and `type` in the body, so you can deduplicate and route before parsing - `X-Formstep-Signature: t={unix seconds},sha256={hex}`, on every callback ## Verify the signature A callback is signed exactly like a webhook: the same header, an HMAC-SHA256 of the timestamp, a dot and the raw body. Use the verifier on [Webhooks reference: Signing](https://formstep.io/docs/developers/webhooks-reference/#signing). Two things differ: - The key is the workspace's request signing secret, one secret for every callback in the workspace. A webhook uses its own secret. - Every callback is signed. A webhook is signed only when it has a secret. To find the secret, open **OAuth and API Keys** in the workspace sidebar and look for the **Request signing secret** card. Only the workspace owner sees it: click **Reveal secret**, then copy it. The owner can read it again at any time. The secret is never returned over the API or MCP, which is why an agent with nobody to set up a receiver should [poll instead](https://formstep.io/docs/developers/mcp-server/#requests-callbacks). > **Regenerating has no grace period** > > When the owner regenerates the secret, the old one stops working at once, even for callbacks being retried. Update your receiver first, then regenerate. When a valid callback fails the check, the cause is almost always the body. Parsing the JSON and serializing it again changes the bytes, so hash the body exactly as it arrived. The other common cause is a regenerated secret the receiver has not picked up yet. > **A resume URL is not authentication** > > A workflow tool's hard-to-guess resume URL can leak into logs, and it does not prove who sent the body. Verify the signature anyway. ## Retries Answer with a 2xx within 10 seconds. A callback gets eight attempts over about four hours, and every attempt sends the same body with the same `id`, as it was when the request ended. | Your response | What Formstep does | | - | - | | 2xx | Done. The callback is marked delivered. | | 408, 429, 5xx | Retries, and honours Retry-After when you send one. | | Timeout or connection error | Retries on the same schedule. | | Any other 4xx | Stops. Your endpoint refused the body, and sending it again cannot help. | | Blocked URL | Stops at once. A host that does not resolve, a private address or a non-HTTPS URL cannot become allowed. Redirects are never followed, so a 3xx stops too. | A callback gets a longer budget than a webhook, because a workflow run is waiting on it: | | Webhook | Request callback | | - | - | - | | Attempts | 5, over about two hours | 8, over about four hours | | A 408 from your endpoint | Stops | Retried | | When attempts run out | After 5 failed deliveries in a row, the integration pauses until someone clicks Reactivate | The request gets a Callback failed badge and the owner one email; nothing pauses | ## When a callback never lands The request page shows the callback URL and what happened to it. `requests.get` carries `callbackFailedAt` while the callback has run out of attempts. The answers are not lost: `requests.get` still returns them. 1. Check the URL. A workflow tool's resume URL belongs to one run, and a run that was deleted or re-created no longer answers on it. 2. Check what your endpoint returned. A 4xx other than 408 or 429 stops the retries at once, so a receiver that rejects a valid body never gets a second try. 3. Fix the receiver, then send the callback again: **Replay** on the request in the [Requests page](https://formstep.io/docs/requests/managing-requests/), or [requests.replayCallback](https://formstep.io/docs/developers/rest-api/#requests-replay-callback). A replay sends the same body with the same `id`, so a receiver that deduplicates is safe. ## Subscriptions hear the same events A callback URL belongs to one request. To send every request of a form to one receiver, subscribe once instead: the Zapier, Make and n8n apps do this for you, and from code you call [webhooks.create](https://formstep.io/docs/developers/rest-api/#webhooks-create) with `request_completed`, `request_expired` or `request_canceled`. A subscription gets the same body, signed with its own secret, under its own event id, with the webhook retry budget. A request with both a callback URL and a subscription sends to both. Test requests reach no subscription, and **Replay** resends only the callback. [Receive and verify the callback](https://formstep.io/docs/developers/quickstart-receive-the-callback/) — Run a receiver on your machine and watch a callback arrive. [Webhook API reference](https://formstep.io/docs/developers/webhooks-reference/) — The answers and display maps, and the verifier. # API reference Every REST API method with its parameters, examples and responses: forms, submissions, share links, requests, webhooks and more. Ends with every error code and reason. POST `https://api.formstep.io/api/v1` > **Single endpoint, many methods** > > Every method is `POST https://api.formstep.io/api/v1` with a JSON body `{"method": "...", "params": {...}}` and an `Authorization: Bearer fs_...` header. See the [Developers overview](https://formstep.io/docs/developers/overview/) for authentication, and [API tokens](https://formstep.io/docs/developers/api-tokens/) for the token itself. The same reference is available as an [OpenAPI 3.1 description](https://formstep.io/docs/openapi.json) for code generators, API clients, and agents. ## Methods [Forms](#forms) - [forms.list](#forms-list) - [forms.get](#forms-get) - [forms.create](#forms-create) - [forms.update](#forms-update) - [forms.publish](#forms-publish) - [forms.unpublish](#forms-unpublish) - [forms.delete](#forms-delete) - [forms.restore](#forms-restore) - [formSettings.get](#form-settings-get) - [formSettings.update](#form-settings-update) [Submissions](#submissions) - [submissions.list](#submissions-list) - [submissions.pdf](#submissions-pdf) - [submissions.sample](#submissions-sample) [Share links](#share-links) - [shareLinks.list](#share-links-list) - [shareLinks.create](#share-links-create) - [shareLinks.update](#share-links-update) [Fields](#fields) - [fields.list](#fields-list) [Requests](#requests) - [requests.create](#requests-create) - [requests.get](#requests-get) - [requests.list](#requests-list) - [requests.cancel](#requests-cancel) - [requests.delete](#requests-delete) - [requests.remind](#requests-remind) - [requests.replayCallback](#requests-replay-callback) - [requests.sample](#requests-sample) - [documents.create](#documents-create) [Connections](#connections) - [connections.list](#connections-list) - [scheduler.listEventTypes](#scheduler-list-event-types) - [paymentConnections.setDefault](#payment-connections-set-default) [Webhooks](#webhooks) - [webhooks.list](#webhooks-list) - [webhooks.create](#webhooks-create) - [webhooks.delete](#webhooks-delete) [Analytics](#analytics) - [analytics.get](#analytics-get) [Workspaces](#workspaces) - [workspaces.list](#workspaces-list) - [workspaces.createInvite](#workspaces-create-invite) - [workspaces.getInvite](#workspaces-get-invite) - [workspaces.updateInvite](#workspaces-update-invite) - [workspaces.revokeInvite](#workspaces-revoke-invite) [Folders](#folders) - [folders.list](#folders-list) - [folders.create](#folders-create) - [folders.update](#folders-update) - [folders.delete](#folders-delete) [Translations](#translations) - [translations.listLanguages](#translations-list-languages) - [translations.addLanguage](#translations-add-language) - [translations.removeLanguage](#translations-remove-language) - [translations.listEntries](#translations-list-entries) - [translations.setEntry](#translations-set-entry) - [translations.deleteEntry](#translations-delete-entry) [Account](#me) - [me.get](#me-get) [Meta](#meta) - [methods.list](#methods-list) ## Conventions - `params` may be omitted; it defaults to `{}`. An unknown method is `404 METHOD_NOT_FOUND`. - A token is bound to one workspace. Naming another workspace, or a form in one, is `403 FORBIDDEN` even when you belong to both. - List methods paginate. They return `{ items, nextCursor, hasMore }`; most also return `canPaginate`, which is `false` when `hasMore` is true but no cursor can resume (fuzzy search). Pass `nextCursor` back as `cursor`. `limit` is 1 to 100, default 20, except for `requests.list`, whose default is 25. - The rate limit is 120 calls per minute per token, shared with the [MCP server](https://formstep.io/docs/developers/mcp-server/); `requests.create` and `documents.create` also share 60 per minute. Failed authentication is limited separately, 30 per 15 minutes per IP, after which bad tokens see `RATE_LIMITED` instead of `UNAUTHORIZED`. - A request body can be at most 1 MiB. Larger bodies are rejected with `VALIDATION_ERROR`. - The path carries the API version. Breaking changes ship as `/api/v2`; new methods and new response fields do not. ## Forms ### forms.list List forms in a workspace. Supports cursor pagination and optional fuzzy name search. **Parameters** `workspaceId` string Required Workspace ID. `folderId` string | null Filter by folder. Pass `null` for root-level forms only. Omit to list all. `query` string Fuzzy name search. Results capped at `limit`; not cursor-paginated. `limit` number Default `20` Page size (1 to 100). `cursor` string Pagination cursor from a previous response. **Example request** ```bash curl -X POST https://api.formstep.io/api/v1 \ -H "Authorization: Bearer fs_..." \ -H "Content-Type: application/json" \ -d '{ "method": "forms.list", "params": { "workspaceId": "ws_abc123" } }' ``` **Example response** 200 Success ```json { "ok": true, "data": { "items": [ { "id": "frm_abc123", "name": "Contact Form", "folderId": null, "workspaceId": "ws_abc123", "isPublished": true, "publishedAt": 1714041851000, "unpublishedAt": null, "createdAt": 1714041800000, "lastEditedAt": 1714042000000, "emoji": null } ], "nextCursor": null, "hasMore": false, "canPaginate": false } } ``` 400 Missing workspaceId 401 Invalid or missing API token 429 Rate limit exceeded ### forms.get Get full details for a single form, including questions, cover, logo, and a preview URL. **Parameters** `formId` string Required Form ID. **Example request** ```bash curl -X POST https://api.formstep.io/api/v1 \ -H "Authorization: Bearer fs_..." \ -H "Content-Type: application/json" \ -d '{ "method": "forms.get", "params": { "formId": "frm_abc123" } }' ``` **Example response** 200 Success ```json { "ok": true, "data": { "id": "frm_abc123", "name": "Event Feedback", "folderId": null, "workspaceId": "ws_abc123", "isPublished": true, "publishedAt": 1714041851000, "unpublishedAt": null, "createdAt": 1714041800000, "lastEditedAt": 1714042000000, "emoji": null, "questions": [ { "id": "q_1", "type": "email-input", "inputType": "email", "title": "Your email", "metadata": null }, { "id": "q_2", "type": "rating-input", "inputType": "rating", "title": "Overall experience", "metadata": { "kind": "rating", "maxStars": 5 } } ], "hasContent": true, "cover": null, "logo": null, "isDeleted": false, "trashedAt": null, "previewUrl": "https://formstep.io/preview/abc..." } } ``` 400 Missing formId 404 Form not found ### forms.create Create a new empty form. Returns the form and a preview URL. **Parameters** `name` string Required Form name (1 to 255 characters). `workspaceId` string Required Workspace ID. `folderId` string Place the form in a folder. Omit to create at workspace root. **Example request** **cURL** ```bash curl -X POST https://api.formstep.io/api/v1 \ -H "Authorization: Bearer fs_..." \ -H "Content-Type: application/json" \ -d '{ "method": "forms.create", "params": { "name": "Contact", "workspaceId": "ws_abc123" } }' ``` **JavaScript** ```ts const res = await fetch('https://api.formstep.io/api/v1', { method: 'POST', headers: { Authorization: `Bearer ${process.env.FORMSTEP_TOKEN}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ method: 'forms.create', params: { name: 'Contact', workspaceId: 'ws_abc123' }, }), }) const { ok, data } = await res.json() ``` **Python** ```python import os, requests res = requests.post( "https://api.formstep.io/api/v1", headers={"Authorization": f"Bearer {os.environ['FORMSTEP_TOKEN']}"}, json={ "method": "forms.create", "params": { "name": "Contact", "workspaceId": "ws_abc123", }, }, ) data = res.json() ``` **Example response** 200 Form created ```json { "ok": true, "data": { "id": "frm_new123", "name": "Contact", "workspaceId": "ws_abc123", "folderId": null, "isPublished": false, "createdAt": 1714041851000, "previewUrl": "https://formstep.io/preview/abc..." } } ``` 400 Missing name or workspaceId 401 Invalid or missing API token ### forms.update Update form metadata: name, folder, emoji, cover, or logo. Does not update form content (use the MCP editor tools for that). **Parameters** `formId` string Required Form ID. `name` string New form name (1 to 255 characters). `folderId` string | null Move form to a folder. Pass `null` to move to workspace root. `emoji` string | null Form emoji (max 10 characters). Pass `null` to clear. `cover` object Cover. `{"type": "color", "color": "#ffffff"}`, `{"type": "image", "url": "https://...", "offsetY": 50}` ( `offsetY` 0 to 100, default 50), or `{"type": "none"}` to remove. Image URLs must be `http(s)` or a `data:image` URI. `logo` object Logo. `{"type": "icon", "name": "HeartIcon"}`, `{"type": "image", "url": "https://..."}`, or `{"type": "none"}` to remove. Icon names are fixed: `QuestionMarkIcon`, `ListBulletsIcon`, `ChartBarIcon`, `ClockCountdownIcon`, `HeartIcon`, `LightbulbIcon`, `CheckCircleIcon`, `MagnifyingGlassIcon`, `TrendUpIcon`, `EnvelopeIcon`, `PhoneIcon`, `CalendarIcon`, `LinkIcon`, `UsersIcon`. Pass at least one of the five updatable fields. **Example request** ```bash curl -X POST https://api.formstep.io/api/v1 \ -H "Authorization: Bearer fs_..." \ -H "Content-Type: application/json" \ -d '{ "method": "forms.update", "params": { "formId": "frm_abc123", "name": "Updated Name", "emoji": "📋" } }' ``` **Example response** 200 Form updated ```json { "ok": true, "data": { "id": "frm_abc123", "name": "Updated Name", "folderId": null, "emoji": "📋" } } ``` `cover` and `logo` come back only when you sent them. A payload that matched the current state on every scalar field adds `noChange: true`. ### forms.publish Publish a form so it can accept responses, and freeze its [field keys](https://formstep.io/docs/developers/requests-api/#field-keys) into a new snapshot. Idempotent: an already-published form returns success with `alreadyPublished: true`, and an unpublished form is republished from its last snapshot. **Parameters** `formId` string Required Form ID. A form with content blocks but no questions publishes with a warning. A form with no content at all cannot be published. Publishing does not create a public URL; call [shareLinks.create](#share-links-create) for that. ### forms.unpublish Take a form offline. Respondents can no longer open it. Idempotent: a form that is not published returns `alreadyUnpublished: true`. Reversible with `forms.publish`. **Parameters** `formId` string Required Form ID. ### forms.delete Move a form to trash. Its active share links are revoked, so their public URLs stop serving. **Parameters** `formId` string Required Form ID. > **Restoring does not bring the links back** > > `forms.restore` returns the form, but the share links it revoked stay revoked. Mint new ones with `shareLinks.create`. A form already in trash returns `alreadyTrashed: true` and keeps its original trash date. ### forms.restore Restore a form from trash. **Parameters** `formId` string Required Form ID. `folderId` string | null Where to restore it. Omit for its original folder, `null` for workspace root, or a folder ID. A form that is not in trash returns `alreadyRestored: true`. ### formSettings.get Read a form’s behavior settings. **Parameters** `formId` string Required Form ID. Returns `{ settings, isDefault, availableEmailDomains, defaultFromAddress, payment }`. `isDefault` is true when the form has no saved settings row yet and you are seeing the defaults. `availableEmailDomains` holds the verified domain ids you can pass as `emailDomainId`, and `payment` reports whether Stripe is connected (connecting it is a dashboard step). ### formSettings.update Update a form’s behavior settings. A partial update: only the fields you send are written. **Parameters** `formId` string Required Form ID. **Access** `language` (BCP-47, default `"en"`), `requireAuthentication`, `showBranding`, `captchaEnabled`, `passwordEnabled`, `password` (4 characters or more; a string implies `passwordEnabled: true`, `null` clears the gate). **Owner notifications** `notifyOnSubmission`, `notificationEmails` (array), `selfNotificationSubject`, `selfNotificationEmailBody`, `pdfGenerationEnabled`, `showViewSubmissionButton`. Owner emails are not translatable, so write them in the language you want. **Respondent notifications** `respondentNotificationEnabled`, `respondentNotificationTo` (the field id of an email question, or `null`), `respondentNotificationSubject`, `respondentNotificationBody`, `respondentNotificationPdfEnabled`. **Reminders** `respondentReminderEnabled`, `respondentReminderTo`, `respondentReminderSubject`, `respondentReminderBody`, `respondentReminderRequiredFieldIds`, and `reminderSteps` (idle offsets like `["1d","3d","1w"]`, at most 5, sorted and deduplicated on save, `[]` for none). The schedule applies to abandoned public-link responses and to requests alike. Pro. **After submit** `redirectUrl` (`http(s)`; `null` or `""` clears), `redirectQueryParams` ( `[{ paramName, fieldId }]`), `allowAnotherResponse` (mutually exclusive with a redirect), `maxSubmissionsPerRespondent` (0 = unlimited, max 1000), `editAfterSubmit`, `maxEdits` (max 3; 0 means unlimited on Pro and Business, 3 on Free). **Retention** `draftRetentionDays` and `submissionRetentionDays` (0 to 36500, `null` reverts to the default). Submission retention is Business, and setting it clears any fixed deletion date configured in the builder. `emailDomainId` string | null A verified email-domain id from `formSettings.get`, for a custom From address. `null` resets to the default sender. Subjects and bodies are plain text and accept `{{variable}}` placeholders; newlines become paragraphs. Customizing a respondent subject or body makes it translatable, so its keys appear in `translations.listEntries` right away. ## Submissions ### submissions.list List a form’s submissions, newest page first, with cursor pagination. **Parameters** `formId` string Required Form ID. `includeDrafts` boolean Default `false` Include responses that were started but never submitted. Drafts are a Pro feature: on Free only completed submissions are listed. `translationLanguage` string Attach stored AI translations of the answers under `items[].translation.display`, keyed like `display`. `items[].answers` and `items[].display` always stay the original. `limit` number Default `20` Page size (1 to 100). `cursor` string Pagination cursor from a previous response. **Example response** 200 Success ```json { "ok": true, "data": { "formId": "frm_abc123", "formName": "Event Feedback", "items": [ { "id": "sub_xyz789", "submittedAt": "2026-05-18 18:02:28", "isCompleted": true, "createdAt": "2026-05-18 18:02:19", "answers": { "email": "user@example.com", "plan": "pro", "contacts": [{ "name": "Ada" }, { "name": "Grace" }] }, "display": { "email": "user@example.com", "plan": "Pro", "contacts": "Ada, Grace" } } ], "nextCursor": null, "hasMore": false, "canPaginate": false } } ``` > **Same answers as webhooks and callbacks** > > Each item carries `answers` keyed by [field key](https://formstep.io/docs/developers/requests-api/#field-keys) and `display` with the same keys as readable text. A [webhook payload](https://formstep.io/docs/developers/webhooks-reference/), a [request callback](https://formstep.io/docs/developers/callbacks/) and `requests.get` carry the same shape. A choice answer is its option key, a repeating group an array of instances. Call `fields.list` for each key’s title and option labels. This method returns no totals. ### submissions.pdf Get a link to one submission’s PDF. Built for the Zapier connector: it returns a result only when the form has an active Zapier integration configured to include the PDF, and the PDF was retained. **Parameters** `formId` string Required Form ID. `submissionId` string Required Submission ID. Must belong to that form and be completed. **Example response** 200 Success ```json { "ok": true, "data": { "url": "https://api.formstep.io/api/storage/...", "filename": "formstep-submission-sub_xyz789.pdf", "contentType": "application/pdf", "byteLength": 148213 } } ``` 404 No retained PDF for a Zapier integration on this submission ### submissions.sample Build a sample submission payload for a form, without any real data. It is the exact shape a share-link submission delivery carries, so connectors use it for field discovery; a request-born submission reaches a subscription as `request.completed` instead, sampled by [requests.sample](#requests-sample). `data.form.snapshotId` is the form’s current published version, the same id live events carry, or `null` while the form is unpublished. **Parameters** `formId` string Required Form ID. **Example response** 200 Sample generated ```json { "ok": true, "data": { "id": "evt_example000000000000", "type": "submission.completed", "createdAt": "2026-05-18T19:00:00.000Z", "apiVersion": "2026-09-24", "test": true, "data": { "form": { "id": "frm_abc123", "name": "Event Feedback", "snapshotId": "js7abc123" }, "submission": { "id": "sub_example000000000000", "respondentEmail": "respondent@example.com", "submittedAt": "2026-05-18T19:00:00.000Z", "updatedAt": null, "editCount": 0, "pdfUrl": null, "language": "en" }, "answers": { "your_email": "john@example.com" }, "display": { "your_email": "john@example.com" } } } } ``` The [webhooks reference](https://formstep.io/docs/developers/webhooks-reference/#payload) explains each field of the payload. ## Share links ### shareLinks.list List share links for a form. **Parameters** `formId` string Required Form ID. `includeRevoked` boolean Include revoked links in the result. `limit` number Default `20` Page size (1 to 100). `cursor` string Pagination cursor. **Example response** 200 Success ```json { "ok": true, "data": { "items": [ { "id": "sl_abc123", "code": "RPjNes52", "url": "https://formstep.io/RPjNes52", "customDomainUrl": null, "formId": "frm_abc123", "createdAt": 1714041851000, "expiresAt": null, "maxClaims": null, "claimedCount": 7, "isRevoked": false, "revokedAt": null, "customDomainId": null, "customSlug": null } ], "availableCustomDomains": [], "nextCursor": null, "hasMore": false, "canPaginate": false } } ``` ### shareLinks.create Create a share link for a form. The form must be published first. **Parameters** `formId` string Required Form ID. A form that is unpublished, or was published and then unpublished, is rejected; call `forms.publish` first. `expiresAt` number Expiration as a future Unix timestamp in milliseconds. Unlike on update, `0` is not accepted here. `maxClaims` number Maximum number of times this link can be used. Must be positive; use `shareLinks.update` to clear it later. The response is the share link (same shape as a `shareLinks.list` item) plus `availableCustomDomains`, so you can follow up with `shareLinks.update` to attach one. **Example request** ```bash curl -X POST https://api.formstep.io/api/v1 \ -H "Authorization: Bearer fs_..." \ -H "Content-Type: application/json" \ -d '{ "method": "shareLinks.create", "params": { "formId": "frm_abc123", "maxClaims": 100 } }' ``` ### shareLinks.update Update a share link. Can change expiration, max claims, custom domain, slug, or revoke the link. **Parameters** `shareLinkId` string Required Share link ID. `expiresAt` number New expiration timestamp in milliseconds. Pass `0` to remove expiration. `maxClaims` number New max claims. Pass `-1` to remove the limit. `customDomainId` string | null Attach a custom domain. Pass `null` to detach. `customSlug` string | null Custom URL slug (3 to 64 characters, lowercase alphanumeric and hyphens). Required together with `customDomainId`; pass both `null` to detach. `login`, `auth-callback`, `preview`, `payment`, `api`, `admin` and `health` are reserved. `revoked` boolean Set to `true` to permanently revoke the link. Cannot be combined with other fields, and cannot be undone. This is the only delete path for a share link. ## Fields ### fields.list List every field of a form’s current published version, with the key to address each one by. Call this before `requests.create` instead of hard-coding keys. See [List the fields](https://formstep.io/docs/developers/requests-api/#discover-fields) for what each flag means. **Parameters** `formId` string Required Form ID. A form that has never been published has no field keys yet and answers `published: false` with no items. **Example request** ```bash curl -X POST https://api.formstep.io/api/v1 \ -H "Authorization: Bearer fs_..." \ -H "Content-Type: application/json" \ -d '{ "method": "fields.list", "params": { "formId": "j57..." } }' ``` **Example response** 200 Success ```json { "ok": true, "data": { "published": true, "items": [ { "key": "company_name", "type": "text", "title": "Company", "required": true, "prefillable": true }, { "key": "company_size", "type": "select", "title": "Company size", "required": false, "prefillable": true, "options": [ { "key": "1_50", "label": "1–50" }, { "key": "51_200", "label": "51–200" } ] }, { "key": "satisfaction", "type": "matrix", "title": "How did we do?", "required": false, "prefillable": true, "rows": [{ "key": "delivery_speed", "label": "Delivery speed" }], "columns": [{ "key": "very_good", "label": "Very good" }, { "key": "poor", "label": "Poor" }] }, { "key": "case_id", "type": "hidden", "title": "Case", "required": false, "prefillable": false, "context": true }, { "key": "total", "type": "number", "title": "Total", "required": false, "prefillable": false, "calculated": true } ], "hasMore": false } } ``` A repeating group is `type: "group"` with `repeating: true` and a `members` array. A Documents block is `type: "documents"` and carries `documents: [{ name }]`, the authored files every respondent already sees. 400 Missing formId 404 Form not found ## Requests A request assigns one published form to one person and calls you back when it ends. How the parameters fit together is explained in [Create requests from code](https://formstep.io/docs/developers/requests-api/). ### requests.create Create a request. Spends one unit of the workspace’s monthly allowance, whether or not the recipient answers. **Parameters** `formId` string The published form to assign. Send this or `form`, not both. `form` object Build the form in this call instead: `{ name, blocks, language?, logo?, cover?, emoji?, theme?, settings?, translations? }`. See [Create a form and a request in one call](https://formstep.io/docs/developers/inline-forms/). `workspaceId` string Where an inline form goes. Defaults to the token’s workspace; any other is refused. `recipient` object `{ email?, name? }`. An email is required when `delivery` is `"email"`; otherwise it only identifies the person in the Requests page and on their answers. `prefill` object Initial answers by field key. The recipient sees them and can change them. `readonly` string\[] Prefilled keys the recipient cannot change. Every key here must also appear in `prefill`, and a locked required field must be prefilled with a non-empty value. `context` object Values for the form’s hidden fields, by field key. Trusted, unchangeable, and echoed back in the callback. An unknown key is rejected with `UNKNOWN_FIELD_KEY`. `metadata` object Your own bookkeeping. Never reaches the form; comes back in callbacks and reads. `language` string One of the form’s published languages. Defaults to the form’s own default. `delivery` string Default `none` `"email"` to have Formstep send the invitation (needs a recipient email, and Pro or Business or one of a Free account’s 10 free invitations, which go only to the account’s own address), or `"none"` to deliver the link yourself. `reminders` string\[] Override the form’s reminder schedule for this request: up to five offsets such as `["2d", "12h", "90m"]`, each at least an hour. An empty array switches reminders off. See [The reminders parameter](https://formstep.io/docs/developers/requests-api/#reminders). `expiresAt` number Epoch milliseconds. Defaults to 30 days out; 365 days is the maximum. `callbackUrl` string Where Formstep POSTs the callback when the request ends. HTTPS only, and the host must resolve to a public address. `externalId` string Your own id for this request. Filterable in `requests.list`. `idempotencyKey` string Repeating it with the same body returns the original request with `deduplicated: true`. A different body is rejected. Keys live 30 days. `domainId` string Mint the link on one of your custom domains. `documents` object\[] `[{ documentId, field?, name? }]`: files handed to this one recipient, uploaded first with [documents.create](#documents-create). `test` boolean Default `false` A dry run: nothing is emailed, the callback carries `"test": true`, and the submission counts nowhere. The link closes within 24 hours, and on Free a workspace may create 10 test requests a day. **Example request** ```bash curl -X POST https://api.formstep.io/api/v1 \ -H "Authorization: Bearer fs_..." \ -H "Content-Type: application/json" \ -d '{ "method": "requests.create", "params": { "formId": "j57...", "recipient": { "email": "ada@acme.com", "name": "Ada" }, "prefill": { "company_name": "Acme" }, "readonly": ["company_name"], "context": { "case_id": "CASE-9" }, "delivery": "email", "externalId": "run-42", "callbackUrl": "https://automation.example/webhook/resume-abc", "idempotencyKey": "run-42" } }' ``` **Example response** 200 Success ```json { "ok": true, "data": { "id": "kd7...", "status": "pending", "url": "https://form.formstep.io/r/rq_...", "deliveryStatus": "queued", "expiresAt": 1794787200000, "createdAt": 1789379200000, "externalId": "run-42", "deduplicated": false } } ``` > **Keep the url** > > `url` carries the one-time token. `requests.get` can usually rebuild it, but it comes back `null` for a request created before the deployment had a request-token key. If you deliver the link yourself, store it when you create it. `deliveryStatus` is one of `not_requested`, `queued`, `sent`, `failed` and `bounced`; see [Delivery status](https://formstep.io/docs/developers/requests-api/#delivery-status). With `form`, the response also carries `form: { id, name, reused, fields: [{ key, type, options? }], aiValidations?, similarForm? }`: the form that was built or reused, the keys its answers come back under, and each choice question’s option keys for a later `prefill`. `aiValidations` lists the keys its [AI Validations](https://formstep.io/docs/building-forms/ai-validation/) report under in `data.aiValidations`, when it has any. `similarForm` appears when the call made a new form although an open one has the same block types in the same order: usually a changed name or title. Send the same definition every time to reuse one form. 400 Unknown field key, wrong value shape, or a locked key that is not prefilled 400 Form not published (FORM\_NOT\_PUBLISHED) or closed (FORM\_CLOSED), or callbackUrl not allowed (CALLBACK\_URL\_NOT\_ALLOWED) 400 An inline form refused: a block fails its schema, Stripe or Cal.com is not connected, or publishing refused it (INLINE\_FORM\_…) 402 Monthly allowance spent (MONTHLY\_ALLOWANCE\_REACHED), Free invitations spent (FREE\_INVITATIONS\_USED) or sent to another address (FREE\_INVITATIONS\_OWN\_ADDRESS\_ONLY), or reminders below Pro 404 Form not found 409 Idempotency key reused with a different body (IDEMPOTENCY\_CONFLICT) 429 More than 60 requests.create calls in a minute on this token, or a Free workspace's 11th test request in a day (TEST\_REQUEST\_LIMIT\_REACHED) ### requests.get Get one request in full: status, outcome, what was prefilled, its timeline, and, once completed, `answers` and `display` keyed by field key, the same two maps the callback carries. **Parameters** `requestId` string Required Request ID. **Example response** 200 Success ```json { "ok": true, "data": { "id": "kd7...", "formId": "j57...", "status": "completed", "outcome": "approve", "isTest": false, "recipient": { "email": "ada@acme.com", "name": "Ada" }, "language": "en", "externalId": "run-42", "metadata": null, "context": { "case_id": "CASE-9" }, "prefill": { "company_name": "Acme" }, "readonlyKeys": ["company_name"], "delivery": "email", "deliveryStatus": "sent", "hasCallback": true, "callbackFailedAt": null, "submissionId": "kp2...", "url": "https://form.formstep.io/r/rq_...", "answers": { "company_name": "Acme", "decision": "approve" }, "display": { "company_name": "Acme", "decision": "Approve" }, "timeline": [ { "id": "kd7...:created", "type": "created", "at": 1789379200000 }, { "id": "kd7...:completed", "type": "completed", "at": 1789465600000 } ], "expiresAt": 1794787200000, "completedAt": 1789465600000 } } ``` > **outcome vs status** > > `status` says whether the request finished; `outcome` says what the recipient decided: `approve`, `decline`, or `changes`. It is `null` on anything but a completed request whose recipient picked one of the three, including a form with no [decision question](https://formstep.io/docs/developers/requests-api/#outcome). The callback URL itself is never returned; `hasCallback` only says whether one is set. The example above is trimmed. A full response also carries `workspaceId`, `formSnapshotId`, `createdVia`, `documents`, `reminderStep`, `remindersSent`, `reminderDueAt`, and the rest of the timestamps (`updatedAt`, `openedAt`, `startedAt`, `lastActivityAt`, `expiredAt`, `canceledAt`, `canceledBy`, `cancelReason`). `callbackFailedAt` is set while this request's callback has run out of attempts, and cleared once one gets through or you replay it. While it is set, the callback was never delivered, so this read is your only copy. Once the form's [retention window](https://formstep.io/docs/submissions-analytics/submission-retention/#retention-and-requests) passes after a request ends, the request is deleted and `requests.get` answers `REQUEST_NOT_FOUND`. An expired or canceled request is deleted without any setting, [30 days after it ended](https://formstep.io/docs/submissions-analytics/submission-retention/#unanswered-requests) by default, so read what you need when its callback arrives. `timeline` is derived, oldest first. Each entry has an `id`, an `at`, and a `type`: `created`, `invitation`, `reminder`, `opened`, `started`, `completed`, `expired`, `canceled`, `callback`. Delivery entries add `deliveryStatus` and `attemptCount`, and callbacks add `eventType`. Delivery rows are kept 30 days, so older timelines thin back to the timestamps. 404 Request not found (REQUEST\_NOT\_FOUND) ### requests.list List requests in a workspace or on one form, newest first. Test requests are left out unless you ask for them. Each row names its form in `formName`, and `formOrigin` is `"inline"` when a request built that form. **Parameters** `workspaceId` string Scope to a workspace. Give this or `formId`. `formId` string Scope to one form. `status` string `pending`, `completed`, `expired`, or `canceled`. `outcome` string `approve`, `decline`, or `changes`. Implies completed requests only. `externalId` string Your own id, to find the request a run created. `includeTest` boolean Default `false` Include requests created with `test: true`. `limit` number Default `25` Page size (1 to 100). `cursor` string Pagination cursor from a previous response. **Example response (trimmed)** 200 Success ```json { "ok": true, "data": { "items": [ { "id": "kd7...", "formId": "j57...", "formName": "Mortgage documents", "formOrigin": "inline", "status": "pending", "outcome": null, "recipient": { "email": "ada@acme.com", "name": "Ada" }, "externalId": "run-42", "deliveryStatus": "sent", "expiresAt": 1794787200000, "createdAt": 1789379200000 } ], "nextCursor": null, "hasMore": false } } ``` List items carry the same fields as `requests.get` minus `url`, `answers`, `display`, and `timeline`, and each one carries `isTest`. Give `workspaceId` or `formId`; sending neither is `400 VALIDATION_ERROR` with reason `SCOPE_REQUIRED`. `outcome` overrides `status`, since only a completed request has a verdict. ### requests.cancel Withdraw a pending request. The link stops working, the recipient sees a withdrawn notice, and a `request.canceled` callback fires. **Parameters** `requestId` string Required Request ID. `reason` string Your note for why, kept on the request and sent in the callback. 200 The canceled request 409 Already completed, expired, or canceled (REQUEST\_NOT\_PENDING) ### requests.delete Delete a request and everything it gathered: the recipient’s submission and answers, uploaded files, the documents you attached, and its timeline and delivery history. Works in any status. A pending request’s link stops working, and no callback fires. Use it when a recipient asks you to erase their data; to withdraw a pending request and hear about it in your callback, use `requests.cancel`. Deleting takes an [API token](https://formstep.io/docs/developers/api-tokens/); an OAuth app, such as an AI agent connected over MCP, gets `403 FORBIDDEN` with reason `API_KEY_REQUIRED`, whatever its scopes. **Parameters** `requestId` string Required Request ID. When the request built its form (an [inline form](https://formstep.io/docs/developers/inline-forms/)) and no other request uses that form, the form is deleted too and `formDeleted` is `true`. Deleting cannot be undone. 200 { id, deleted: true, formDeleted } 403 Called with an OAuth app's token (API\_KEY\_REQUIRED) 404 No such request in a workspace you can reach (REQUEST\_NOT\_FOUND) ### requests.remind Email the recipient now, without touching the reminder schedule. Needs a recipient email and a Pro or Business plan. **Parameters** `requestId` string Required Request ID. Must still be pending, and not a test request. Manual reminders must be at least 10 minutes apart, and a request gets at most 8 reminders in total, manual and scheduled together. The automatic schedule is untouched: `reminderStep` and `reminderDueAt` stay where they were. 200 The request, with remindersSent incremented 400 No recipient email on the request (RECIPIENT\_EMAIL\_REQUIRED) 402 Request reminders require Pro or Business (UPGRADE\_REQUIRED) 409 Not pending (REQUEST\_NOT\_PENDING), too soon (REMINDER\_TOO\_SOON, with details.retryAfterMs), cap reached (REMINDER\_CAP\_REACHED), or a test request (TEST\_REQUEST) ### requests.replayCallback Re-send the callback a request fired when it ended, with the same payload and the same event id, so a receiver that already handled it can deduplicate. Use it after fixing a broken endpoint. **Parameters** `requestId` string Required Request ID. Must be completed, expired, or canceled. **Example response** 200 Success ```json { "ok": true, "data": { "dispatchId": "kf9...", "eventId": "evt_kf9..." } } ``` 409 Still pending, so there is no terminal callback to replay (REQUEST\_NOT\_TERMINAL) 409 The request was created without a callbackUrl (NO\_CALLBACK\_TO\_REPLAY) ### requests.sample Build a sample request event for a form, without any real request. It is the exact envelope a `request_*` subscription created with [webhooks.create](#webhooks-create) receives, so connectors use it for field discovery. A completed sample carries the same example answers [submissions.sample](#submissions-sample) shows; an expired or canceled sample carries the request block alone. **Parameters** `formId` string Required Form ID. `eventType` string Required Which ending to sample, in the `webhooks.create` spelling. The envelope’s `type` is the dotted form. One of `request_completed` `request_expired` `request_canceled` **Example response** 200 Sample generated ```json { "ok": true, "data": { "id": "evt_example000000000000", "type": "request.completed", "createdAt": "2026-05-18T19:00:00.000Z", "apiVersion": "2026-09-24", "test": true, "data": { "request": { "id": "req_example000000000000", "externalId": "order-1234", "status": "completed", "language": "en", "recipient": { "email": "recipient@example.com", "name": "Sample Recipient" }, "metadata": { "source": "sample" }, "context": {}, "createdAt": "2026-05-18T18:00:00.000Z", "completedAt": "2026-05-18T19:00:00.000Z" }, "form": { "id": "frm_abc123", "name": "Event Feedback", "snapshotId": "js7abc123" }, "submission": { "id": "sub_example000000000000", "respondentEmail": "respondent@example.com", "submittedAt": "2026-05-18T19:00:00.000Z", "updatedAt": null, "editCount": 0, "pdfUrl": null, "language": "en" }, "answers": { "your_email": "john@example.com" }, "display": { "your_email": "john@example.com" } } } } ``` The request block and the outcome are documented under [Request callbacks](https://formstep.io/docs/developers/callbacks/#payload); the submission half on the [webhooks reference](https://formstep.io/docs/developers/webhooks-reference/#payload). Sample ids are the fixed placeholders shown above and `test` is `true`, so a receiver can tell a sample from a live event. ### documents.create Reserve an upload for a file you will hand to one recipient through the form’s [Documents block](https://formstep.io/docs/building-forms/documents-block/). You upload the bytes outside this API: you get a presigned `PUT` URL, upload the file to it, and `requests.create` verifies the object before the request exists. **Parameters** `formId` string Required The form whose Documents block will show the file. Scopes the upload to that workspace. `name` string Required Display name the recipient sees (1 to 200 characters). Overridable per request. `contentType` string Required `application/pdf` or an image type: `image/png`, `image/jpeg`, `image/webp`, `image/gif`, `image/avif`, `image/bmp`, `image/tiff`. Office documents are not accepted. `size` number Required Exact byte length. Maximum 25 MB (26,214,400). `sha256` string Hex digest of the bytes. Verified after upload when given. **Example response** 200 Upload reserved ```json { "ok": true, "data": { "id": "kn7...", "name": "Lease contract draft", "contentType": "application/pdf", "size": 412000, "uploadUrl": "https://...", "expiresAt": 1792198800000 } } ``` `PUT` the raw bytes to `uploadUrl` within the hour, with `Content-Type` set to the type you declared, then reference the id from `requests.create`: ```json { "method": "requests.create", "params": { "formId": "j57...", "documents": [ { "documentId": "kn7...", "name": "Your lease contract" }, { "documentId": "kn8...", "field": "attachments" } ] } } ``` - `field` is the Documents block’s field key. Optional when the form has exactly one block; required with two or more. - The block’s authored documents stay; yours appear below them, for this one recipient. - Caps: 25 MB per document, 100 MB of documents per request, 20 documents shown per block including the authored ones. - One upload can be referenced by any number of requests. An upload nobody references ages out. Bytes count against the workspace owner’s storage until the last request referencing them is stripped by retention. Every failure here is `400 VALIDATION_ERROR`. The reasons, from this method and from `requests.create`, are listed under [Documents](#reasons-documents) in the error reference. ## Connections ### connections.list The Stripe and Cal.com accounts a workspace has connected. Read it before building an inline form with a payment or a booking: a person connects each service once, on the page `connectUrl` names, and an automation cannot. **Parameters** `workspaceId` string Required Workspace ID. **Example response** 200 Success ```json { "ok": true, "data": { "connectUrl": "https://app.formstep.io/k57.../integrations", "items": [ { "provider": "stripe", "id": "pc7...", "name": "Acme Ltd", "status": "connected", "ready": true, "default": true }, { "provider": "cal.com", "id": "oc7...", "name": "ada@acme.com", "status": "active", "ready": true } ], "nextCursor": null, "hasMore": false, "canPaginate": false } } ``` `ready` says whether forms can use the account now. `default` marks the Stripe account payments charge through unless a payment block names another. ### scheduler.listEventTypes The Cal.com event types a booking block can name, and whether each can be booked from a form. **Parameters** `workspaceId` string Required Workspace ID. `connection` string The Cal.com connection id from `connections.list`. Required when the workspace has several. Returns `{ connection: { id, name }, items: [{ id, title, durationMinutes?, bookable, reasons? }] }`. Only a bookable event type publishes; `reasons` says why the others are not. 400 No Cal.com connection, an expired one, or several and none named. The message says which, and where to connect. ### paymentConnections.setDefault Make a Stripe account the workspace default, the one payment blocks charge through unless they name another. Only the workspace owner can change the default. A token of any other member gets `403 FORBIDDEN`. **Parameters** `workspaceId` string Required Workspace ID. `connection` string Required A Stripe connection id of that workspace, from `connections.list`. 200 { connection }, the new default 404 The connection belongs to another workspace, or does not exist ## Webhooks ### webhooks.list List webhook subscriptions for a form. **Parameters** `formId` string Required Form ID. **Example response** 200 Success ```json { "ok": true, "data": { "items": [ { "subscriptionId": "int_abc123", "formId": "frm_abc123", "provider": "zapier", "targetUrl": "https://hooks.zapier.com/...", "eventType": "submission_created", "status": "active", "createdAt": 1714041851000 } ], "hasMore": false } } ``` ### webhooks.create Subscribe a URL to form events: new or abandoned submissions, or requests on the form ending. The URL must use HTTPS. **Parameters** `formId` string Required Form ID. `targetUrl` string Required HTTPS URL to receive webhook payloads. `provider` string Required Which tool the subscription belongs to. It is a label for your own bookkeeping. There is no marketplace app to install, and every provider behaves the same. One of `zapier` `make` `n8n` `eventType` string Default `submission_created` Event type to subscribe to. The three `submission_*` types deliver the submission payload: `submission_created` a first submission, `submission_updated` a respondent’s edit, and `submission_abandoned` an idle draft. The three `request_*` types deliver the matching [request event](https://formstep.io/docs/developers/callbacks/#payload) whenever a request on the form ends that way, signed with this subscription’s secret; test requests reach no subscription. One of `submission_created` `submission_updated` `submission_abandoned` `request_completed` `request_expired` `request_canceled` `idleWindow` string Required when `eventType` is `submission_abandoned`; rejected for every other type. One of `12h` `1d` `3d` `1w` `signingSecret` string Optional HMAC signing secret, 32 to 255 characters. When supplied, deliveries include `X-Formstep-Signature`. The secret is stored but never returned by the API. **Example request** ```bash curl -X POST https://api.formstep.io/api/v1 \ -H "Authorization: Bearer fs_..." \ -H "Content-Type: application/json" \ -d '{ "method": "webhooks.create", "params": { "formId": "frm_abc123", "targetUrl": "https://hooks.zapier.com/hooks/catch/...", "provider": "zapier", "eventType": "submission_created", "signingSecret": "whsec_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" } }' ``` **Example response** 200 Webhook created ```json { "ok": true, "data": { "subscriptionId": "int_new123", "formId": "frm_abc123", "provider": "zapier", "targetUrl": "https://hooks.zapier.com/hooks/catch/...", "eventType": "submission_created" } } ``` Abandoned-submission subscriptions return the selected `idleWindow` from both `webhooks.create` and `webhooks.list`. Every other subscription omits it. A request subscription hears the same events a [callback](https://formstep.io/docs/developers/callbacks/) does, but as its own delivery: its own event id, its own signature, and its own five-attempt retry budget, after which the subscription pauses. A request created with a `callbackUrl` on a form with a `request_completed` subscription therefore fires twice, once to each receiver. `requests.replayCallback` re-sends the callback only. Use [requests.sample](#requests-sample) to see the payload before any request has ended. ### webhooks.delete Remove a webhook subscription. **Parameters** `subscriptionId` string Required Subscription ID from `webhooks.list` or `webhooks.create`. **Example response** 200 Webhook deleted ```json { "ok": true, "data": { "subscriptionId": "int_abc123", "deleted": true } } ``` ## Analytics ### analytics.get Get aggregated analytics metrics for a form. Supports date range, device, traffic source, and country filters. Analytics are a Pro feature, and the rule follows the workspace owner’s plan, the same way the dashboard’s Analytics tab does. If the owner is not on Pro, this returns `UPGRADE_REQUIRED`, including for history recorded while they were. A free member of a Pro owner’s workspace gets the data. **Parameters** `formId` string Required Form ID. `from` number Start of date range as Unix timestamp in milliseconds. Must be less than or equal to `to` when both are set. `to` number End of date range as Unix timestamp in milliseconds. Omit both for all time; `period` then comes back as `{ "from": null, "to": null }`. `device` string Default `all` Filter by device type. One of `all` `desktop` `mobile` `tablet` `trafficSource` string Filter by traffic source (e.g. `"Direct"`, `"Google"`). `country` string Filter by 2-letter country code (e.g. `"US"`, `"DE"`). `includeEvents` boolean Default `false` Also return the sanitized analytics events behind the metrics, for your own analysis. No visitor ids. Rates are numbers from 0 to 100, counts are integers, and `totalEvents` is the raw event row count before deduplication into unique visitors. **Example response** 200 Success ```json { "ok": true, "data": { "formId": "frm_abc123", "period": { "from": null, "to": null }, "totalEvents": 17, "metrics": { "views": 7, "uniqueVisitors": 7, "engaged": 6, "submissions": 4, "engagementRate": 86, "completionRate": 57, "bounceRate": 14 }, "breakdown": { "byBrowser": { "Chrome": 7 }, "byCountry": { "US": 4, "DE": 3 }, "byDevice": { "desktop": 6, "mobile": 1 }, "bySource": { "Direct": 5, "Google": 2 } } } } ``` ## Workspaces ### workspaces.list List the workspaces your token can reach. No parameters. An API token is bound to one workspace, so this returns exactly that one, even when your account belongs to several. **Parameters** **Example response** 200 Success ```json { "ok": true, "data": { "items": [ { "id": "ws_abc123", "name": "Acme Inc", "createdAt": 1714041800000, "role": "owner" } ], "nextCursor": null, "hasMore": false, "canPaginate": false } } ``` ### workspaces.createInvite Create an invite link for a workspace. Invite methods take an [API token](https://formstep.io/docs/developers/api-tokens/) from the workspace owner; an OAuth app gets `403 FORBIDDEN` with reason `API_KEY_REQUIRED`, whatever its scopes. **Parameters** `workspaceId` string Required Workspace ID. `expiresAt` number Expiration as a future Unix timestamp in milliseconds. `maxUses` number Maximum number of times the invite can be used. **Example response** 200 Invite created ```json { "ok": true, "data": { "id": "inv_abc123", "workspaceId": "ws_abc123", "code": "aBcDeFgH", "expiresAt": null, "maxUses": null, "uses": 0, "createdAt": 1714041851000 } } ``` ### workspaces.getInvite Get one workspace invite. Returns the same shape as `workspaces.createInvite`. **Parameters** `inviteId` string Required Invite ID. 404 Invite not found ### workspaces.updateInvite Update an existing workspace invite. Provide at least one of `expiresAt` or `maxUses`, or the call is rejected. Returns the updated invite. **Parameters** `inviteId` string Required Invite ID. `expiresAt` number New expiration timestamp in milliseconds. `maxUses` number New max uses limit. ### workspaces.revokeInvite Permanently revoke a workspace invite. **Parameters** `inviteId` string Required Invite ID. **Example response** 200 Invite revoked ```json { "ok": true, "data": { "inviteId": "inv_abc123", "revoked": true } } ``` ## Folders ### folders.list List folders in a workspace. **Parameters** `workspaceId` string Required Workspace ID. `limit` number Default `20` Page size (1 to 100). `cursor` string Pagination cursor. **Example response** 200 Success ```json { "ok": true, "data": { "items": [ { "id": "fld_abc123", "name": "Customer Feedback", "workspaceId": "ws_abc123", "parentId": null, "createdAt": 1714041800000 } ], "nextCursor": null, "hasMore": false, "canPaginate": false } } ``` ### folders.create Create a folder in a workspace. Idempotent: returns the existing folder if a folder with the same name already exists. **Parameters** `workspaceId` string Required Workspace ID. `name` string Required Folder name (1 to 255 characters). `parentId` string | null Parent folder ID for nesting. Omit for root level. **Example response** 200 Folder created ```json { "ok": true, "data": { "id": "fld_new123", "name": "Customer Feedback", "workspaceId": "ws_abc123", "parentId": null, "createdAt": 1714041851000, "alreadyExisted": false } } ``` ### folders.update Rename a folder or move it to a different parent. **Parameters** `folderId` string Required Folder ID. `name` string New folder name (1 to 255 characters). `parentId` string | null New parent folder. Pass `null` to move to root. ### folders.delete Delete a folder and all its subfolders. The forms inside move to the Trash, where they can be restored for 30 days. **Parameters** `folderId` string Required Folder ID. > **Destructive operation** > > The folder and its subfolders are deleted for good. Their forms are not: live forms move to the Trash and come back out with `forms.restore`; forms already in the Trash stay there. `trashedFormIds` lists the forms that moved; `deletedFormIds` repeats it for older callers and is deprecated. **Example response** 200 Folder deleted ```json { "ok": true, "data": { "folderId": "fld_abc123", "deletedFolderIds": ["fld_abc123"], "trashedFormIds": ["frm_in_folder"], "deletedFormIds": ["frm_in_folder"], "message": "Folder and 0 subfolder(s) deleted; 1 form(s) moved to the Trash." } } ``` ## Translations ### translations.listLanguages List all languages configured on a form. **Parameters** `formId` string Required Form ID. **Example response** 200 Success ```json { "ok": true, "data": { "formId": "frm_abc123", "items": [ { "language": "es", "completion": { "total": 15, "current": 12, "outdated": 2, "missing": 1, "suggested": 0 }, "lastUpdatedAt": 1714041851000 } ], "nextCursor": null, "hasMore": false, "canPaginate": false } } ``` ### translations.addLanguage Register a language on a form. Every other translation method fails with `404 NOT_FOUND` until you do. **Parameters** `formId` string Required Form ID. `language` string Required BCP-47 language tag (e.g. `"es"`, `"pt-BR"`). **Example response** 200 Language added ```json { "ok": true, "data": { "formId": "frm_abc123", "language": "es", "rowId": "tl_new123" } } ``` ### translations.removeLanguage Remove a language and all its translations from a form. **Parameters** `formId` string Required Form ID. `language` string Required BCP-47 language tag. ### translations.listEntries List every source key for one language on a form, with its current state. This is how you discover the `key` values `translations.setEntry` takes. **Parameters** `formId` string Required Form ID. `language` string Required BCP-47 language tag. Must already be on the form. **Example response** 200 Success ```json { "ok": true, "data": { "formId": "frm_abc123", "language": "es", "completion": { "total": 15, "current": 12, "outdated": 2, "missing": 1, "suggested": 0 }, "items": [ { "key": "block_q_1.title", "status": "current", "value": "[{\"text\":\"Tu correo electrónico\"}]", "sourceFragment": "[{\"text\":\"Your email\"}]", "updatedAt": 1714041851000, "block": { "id": "q_1", "type": "email-input", "label": "Your email" } } ], "hasMore": false } } ``` `status` is `missing` (nothing stored), `outdated` (the source changed since), `current`, or `suggested` (an AI suggestion staged but not accepted). Keys cover form content (`block_.*`) and, once an author has customized them, the respondent confirmation and reminder emails (`email.confirmation.*`, `email.reminder.*`). ### translations.setEntry Set a single translation entry. The language must have been added via `translations.addLanguage` first. **Parameters** `formId` string Required Form ID. `language` string Required BCP-47 language tag. `key` string Required A key from `translations.listEntries`. Do not construct one by hand. `value` string Required The translated fragment, JSON-stringified. Its mark structure must match the source fragment. **Example request** ```bash curl -X POST https://api.formstep.io/api/v1 \ -H "Authorization: Bearer fs_..." \ -H "Content-Type: application/json" \ -d '{ "method": "translations.setEntry", "params": { "formId": "frm_abc123", "language": "es", "key": "block_q_1.title", "value": "[{\"text\":\"Tu correo electrónico\"}]" } }' ``` > **Writes here are live** > > The API has no draft-then-publish step: a `setEntry` or `deleteEntry` reaches respondents immediately. The dashboard and the MCP translation tools use a draft instead. Returns `{ formId, language, key }`. ### translations.deleteEntry Delete a single translation entry, reverting that key to the form’s default language. Idempotent. When the last entry for a language goes, the language drops off the form’s published languages. **Parameters** `formId` string Required Form ID. `language` string Required BCP-47 language tag. `key` string Required Translation key to delete. ## Account ### me.get Get information about the authenticated user. **Parameters** **Example response** 200 Success ```json { "ok": true, "data": { "id": "usr_abc123", "email": "you@example.com", "name": "Jane Doe" } } ``` ## Meta ### methods.list List every method name this deployment serves, sorted. The authoritative answer when this page and the server disagree. **Parameters** **Example response** 200 Success ```json { "ok": true, "data": { "methods": [ "methods.list", "analytics.get", "folders.create", "folders.delete", "folders.list", "folders.update", "formSettings.get", "formSettings.update", "forms.create", "forms.delete", "forms.get", "forms.list", "..." ] } } ``` ## Error reference Every error response has the same shape. `code` says what kind of failure it is, and `details.reason` says what caused it. The set of codes is closed: a new failure adds a reason, never a code. Branch on `code`, and read `reason` to know what to fix. ```json { "ok": false, "error": { "code": "VALIDATION_ERROR", "message": "No field with key \"company\" on this form.", "details": { "reason": "UNKNOWN_FIELD_KEY", "field": "prefill.company", "validKeys": ["company_name", "contacts"] } } } ``` `details` is there whenever the server can name the cause. Besides `reason`, it may carry `field` (the parameter at fault, dotted for nesting), `validKeys`, `validValues` (the values a parameter accepts), `expectedType`, `feature` (on `UPGRADE_REQUIRED`), and `retryAfterMs` (on a throttled call). ### Codes | Code | Status | What it means | | - | - | - | | VALIDATION\_ERROR | 400 | Invalid or missing parameters, or a body over 1 MiB. | | UNAUTHORIZED | 401 | Missing or invalid API token. | | UPGRADE\_REQUIRED | 402 | The plan does not include the feature, or a limit of the plan is spent. The reason says which. | | FORBIDDEN | 403 | The token cannot reach the resource, such as a form in another workspace. | | NOT\_FOUND | 404 | The resource does not exist. | | METHOD\_NOT\_FOUND | 404 | Unknown method name. `methods.list` lists the ones there are. | | CONFLICT | 409 | The resource is not in a state that allows the call, such as a request that is no longer pending, or an idempotency key reused with a different body. | | RATE\_LIMITED | 429 | Too many calls: over a token's [rate limit](https://formstep.io/docs/developers/overview/#rate-limits), over an email limit, or too many failed authentications from this IP. Retry after `retryAfterMs`. | | INTERNAL\_ERROR | 500 | Unexpected server error. Try again later. | ### Reasons when you create a request These come from `requests.create`. Each is a `VALIDATION_ERROR` unless the row names another code. An [inline form](https://formstep.io/docs/developers/inline-forms/) adds its own `INLINE_FORM_*` reasons, listed under [When the call is refused](https://formstep.io/docs/developers/inline-forms/#errors). | Reason | What to do | | - | - | | FORM\_TRASHED | Restore the form from the trash. A form in the trash takes no new requests. | | FORM\_NOT\_PUBLISHED | Publish the form. A request pins a published version, so there has to be one. | | FORM\_CLOSED | Reopen the form in Settings, Submissions tab, Accepting submissions. A closed form takes no new requests. | | UNKNOWN\_FIELD\_KEY | No field has that key, or it belongs in another parameter: every context key must be a hidden field on the published form, and free-form keys go in metadata. validKeys lists the accepted keys. A key that used to work was changed or deleted in the editor. | | CONTEXT\_KEY\_NOT\_HIDDEN\_FIELD | A visible question or a calculated field was sent in context. Visible questions go in prefill; calculated fields take nothing. | | INVALID\_PREFILL\_VALUE | Wrong shape for the question type; expectedType says what was wanted. Send the option key, not the label, and { "row\_key": "column\_key" } for a matrix. expectedType "not\_prefillable" means the field takes no value from you: a file, signature, payment, appointment or calculated field. | | READONLY\_REQUIRES\_PREFILL | A locked key has no value. Every key in readonly must also be in prefill. | | READONLY\_REQUIRED\_EMPTY | A required field is locked with an empty value, so the recipient could never submit. Send a value or stop locking it. | | LANGUAGE\_NOT\_PUBLISHED | That language is not published on the current version. validKeys lists the ones that are. | | INVALID\_REMINDER\_SCHEDULE | An offset could not be read or is under one hour, an offset appears twice, or there are more than five. Use whole days, hours or minutes: 2d, 12h, 90m. | | EXPIRY\_OUT\_OF\_RANGE | expiresAt is in the past or more than 365 days out. | | CALLBACK\_URL\_NOT\_ALLOWED | The URL is not HTTPS, carries credentials, or resolves to a private address. Localhost does not work, so use a tunnel. | | DOMAIN\_NOT\_ALLOWED | That custom domain is not active, or belongs to another workspace. | | RECIPIENT\_EMAIL\_REQUIRED | Email delivery and a reminder schedule need recipient.email. | | RECIPIENT\_NAME\_TOO\_LONG | recipient.name is over 100 characters. The invitation greets the recipient by it, so send their name only. | | IDEMPOTENCY\_CONFLICT | CONFLICT. That key was used with a different body. Use a new key, or send the original body again unchanged. | | UPGRADE\_REQUIRED | UPGRADE\_REQUIRED. The plan does not include the feature: invitations and reminders need Pro or Business. | | FREE\_INVITATIONS\_USED | UPGRADE\_REQUIRED. This Free account has spent its free invitations. Create the request with "delivery": "none" and send the link yourself, or upgrade. | | FREE\_INVITATIONS\_OWN\_ADDRESS\_ONLY | UPGRADE\_REQUIRED. On Free, invitations go only to your own address, which validValues names. Send the link yourself, or upgrade. | | MONTHLY\_ALLOWANCE\_REACHED | UPGRADE\_REQUIRED. The workspace has spent this month's allowance. Requests already created can still be answered; new ones wait for the 1st of the month (UTC) or an upgrade. | | TEST\_REQUEST\_LIMIT\_REACHED | RATE\_LIMITED. A Free workspace has created its 10 test requests for today. | | RECIPIENT\_EMAIL\_LIMITED | RATE\_LIMITED. This address has had the most emails one form may send it in a day. Retry after retryAfterMs. | | INVITATION\_RATE\_LIMITED | RATE\_LIMITED. The account has sent its hourly share of invitations and reminders. Retry after retryAfterMs. | ### Documents From `documents.create` and from the `documents` parameter of `requests.create`. Each is a `VALIDATION_ERROR` except `DOCUMENT_NOT_FOUND`, which is `NOT_FOUND`. | Reason | What to do | | - | - | | DOCUMENT\_TYPE\_NOT\_ALLOWED | Send a PDF or an image. Office documents are not accepted. | | DOCUMENT\_TOO\_LARGE | One document may be 25 MB. | | INVALID\_DOCUMENT\_NAME | The name must be 1 to 200 characters. | | INVALID\_DOCUMENT\_SHA256 | sha256 must be 64 hex characters. | | DOCUMENT\_NOT\_FOUND | No upload with that documentId in this workspace, or it aged out unused. | | DOCUMENT\_NOT\_UPLOADED | The upload was reserved, but the bytes never arrived. PUT the file to its uploadUrl first. | | DOCUMENT\_INVALID | The uploaded bytes disagree with the size, type or sha256 you declared, or the file is not a PDF or an image. | | INVALID\_DOCUMENT\_TARGET | The form has no Documents block, or it has several and field does not name one. validKeys lists the block keys. | | DOCUMENTS\_TOO\_MANY | The block would show more than 20 documents, counting the ones the form already has. | | DOCUMENTS\_TOO\_LARGE | One request may carry 100 MB of documents in total. | ### Reasons on an existing request From `requests.get`, `requests.list`, `requests.cancel`, `requests.delete`, `requests.remind` and `requests.replayCallback`. Each is a `CONFLICT` unless the row names another code. | Reason | What to do | | - | - | | REQUEST\_NOT\_FOUND | NOT\_FOUND. No request with that id in a workspace this token can reach, or it was deleted. | | SCOPE\_REQUIRED | VALIDATION\_ERROR. requests.list needs a workspaceId or a formId. | | API\_KEY\_REQUIRED | FORBIDDEN. requests.delete takes an API token. An OAuth app, such as an agent connected over MCP, cannot delete. | | REQUEST\_NOT\_PENDING | The request is already completed, expired or canceled. A finished request cannot be reminded or canceled. | | REQUEST\_EXPIRED | The expiry has passed, but the status has not caught up yet. A reminder would point at a link that no longer opens. | | REMINDER\_TOO\_SOON | A manual reminder went out less than 10 minutes ago. Wait retryAfterMs. | | REMINDER\_CAP\_REACHED | The request has had all 8 reminders it will ever get, manual and scheduled together. | | TEST\_REQUEST | Nothing is ever emailed for a test request. Open its link yourself instead. | | REQUEST\_NOT\_TERMINAL | The request is still pending, so there is no callback to replay yet. | | NO\_CALLBACK\_TO\_REPLAY | The request was created without a callbackUrl. | ## Next steps [Create requests from code](https://formstep.io/docs/developers/requests-api/) — How the request parameters fit together. [Webhook API reference](https://formstep.io/docs/developers/webhooks-reference/) — Payload schema and signing # API tokens Create, manage, and rotate tokens for the API and MCP server. Learn the token format, the limits on each token, and what to do first if one leaks. ## Format A token is `fs_` followed by 32 alphanumeric characters (35 in total). Send it as a bearer token: ```text Authorization: Bearer fs_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ``` The same header works for [the API](https://formstep.io/docs/developers/rest-api/) and the [MCP server](https://formstep.io/docs/developers/mcp-server/). Only a SHA-256 hash is stored, so a lost token cannot be recovered. Create a new one. ## Limits - Each person can hold 10 tokens per workspace. Access tokens issued when you connect an OAuth app (`fso_...`) sit on the same page but do not count. - You pick the expiry when you create the token: 30 days, 90 days or No expiration. There is no extend button: to change it, create a new token and delete the old one. A token that runs an unattended integration, such as an n8n workflow, should use No expiration. - A token is bound to the workspace it was created in. A call that names another workspace, or a form in one, fails with `FORBIDDEN`, even when you are a member of both. - A token has no scopes: it does everything you can do in that workspace. There is no read-only token. > **Every plan has API access** > > Creating and using tokens is not limited by plan. Individual calls still are: scheduling request reminders needs Pro or Business, for example, and answers with `UPGRADE_REQUIRED` otherwise. ## Create a token 1. **Open OAuth and API Keys** In the sidebar, under your workspace. Tokens live in the API Keys card. 2. **Click Create token** Name it in the Create API token dialog, for example "CI/CD Pipeline" or "n8n production", and pick how long it lasts under Expiration: 30 days, 90 days or No expiration. 3. **Copy it now** The API token created dialog shows the value once. Copy & Close, and the value is gone for good. 4. **Store it in your secrets manager** Never commit it, and never ship it to a browser. The list then shows the name, the first 11 characters, when it was created, and when it expires, or **Never expires**. An expired token is marked **Expired** and stops authenticating. ## Rotate a token 1. **Create the replacement** Give it a name that says which generation it is, like "n8n production v2". 2. **Roll it out** Update every consumer. Both tokens work while you switch. 3. **Delete the old one** Use the trash icon on its row and confirm in Delete API token?. ## Revoke a token Deleting a token removes it permanently and it stops working on the next call. There is no grace period, and anything still using it starts getting `401 UNAUTHORIZED`. You can also rename a token with the pencil icon (up to 64 characters); renaming does not change its value. Only you see your own tokens. Workspace admins cannot list or delete them. Leaving the workspace, or being removed from it, deletes them for you. > **A token is full access** > > Anyone holding it can act as you inside that workspace. If one leaks, delete it first and investigate second. ## OAuth instead A third-party app connecting on behalf of a user should use the OAuth flow instead of asking for a token. It gets an `fso_...` access token, valid one hour and refreshable for 30 days, bound to the one workspace the user picked at consent. The user sees and disconnects those apps in **Connected apps** on the same page. See [MCP server](https://formstep.io/docs/developers/mcp-server/#oauth). ## Next steps [MCP server](https://formstep.io/docs/developers/mcp-server/) — Use Formstep from AI agents [Webhooks reference](https://formstep.io/docs/developers/webhooks-reference/) — Payload schema and signing # MCP server Use Formstep from Claude, ChatGPT, Cursor and other MCP clients. Connect with OAuth or an API token so AI agents can build forms, send requests and read the answers. MCP is an open standard that lets AI tools call outside services. Formstep hosts an MCP server that any client with remote MCP support can connect to, such as Claude, ChatGPT and Cursor. To connect one step by step, see [Connect an AI agent](https://formstep.io/docs/guides/ai-agents/connect/). ## Connection ```text URL: https://api.formstep.io/api/mcp (POST, streamable HTTP) Auth: Bearer ``` Two kinds of bearer token work: - An [API token](https://formstep.io/docs/developers/api-tokens/) (`fs_...`), for your own scripts and headless agents. - An OAuth access token (`fso_...`), issued by the [OAuth flow](#oauth). Most clients get one for you when you sign in. Both reach exactly one workspace. A tool call that names another workspace, or a form in one, fails with `FORBIDDEN`. OAuth tokens also carry scopes: `mcp:read` reaches the tools that only read, and `mcp:write` reaches every tool. `tools/list` shows only the tools a token may call, and any other call answers `403` with `insufficient_scope`. API tokens have no scopes and reach every tool. Tool calls share the limit of 120 a minute per token with the [REST API](https://formstep.io/docs/developers/rest-api/). Only `tools/call` counts; `initialize`, `tools/list`, `prompts/*` and `resources/*` are free. Over the limit, the call still returns HTTP 200, with a failed tool result carrying `RATE_LIMITED` and `retryAfterMs`. Poll on a timer, never in a loop. ## Tools The server describes its own tools on `tools/list`, each with a full schema, and clients that load schemas on demand fetch one when a task needs it. The tools cover forms and the editor, themes, settings, share links, translations, folders, webhooks, analytics, submissions and requests. Longer guides, such as every question type or how logic rules work, are served as `skill://` resources and through the `load_skill` tool, so an agent reads them when it needs them. ## Requests An agent sends a request the same way code does: `fields_list`, then `request_create`, with the same parameters as the REST API. [Create requests from code](https://formstep.io/docs/developers/requests-api/#agents) covers the parameters and what is different over MCP, and [Inline forms](https://formstep.io/docs/developers/inline-forms/) covers building the form in the same call. ### Callbacks or polling With a `callbackUrl`, Formstep POSTs once when the request ends, signed with the workspace's request signing secret; see [Request callbacks](https://formstep.io/docs/developers/callbacks/). That secret is shown only on the **OAuth and API Keys** page, and never returned over MCP or the API. An agent that runs on its own, with nobody to set up a receiver, therefore cannot verify a callback. It should leave out `callbackUrl` and poll `request_get` every few minutes until `status` leaves `pending`, then read `answers` and `outcome`. `expiresAt` bounds how long that is worth doing. ## Tools that ask first Every tool carries the MCP hints `readOnlyHint`, `destructiveHint` and `openWorldHint`. These are marked destructive, because undoing them takes another call or is not possible: `form_delete`, `form_unpublish`, `workspaceFolder_delete`, `editor_deleteElement`, `translationLanguage_delete` and `request_cancel`. So are the tools that send something outside the workspace, such as `request_create`, and the tools that replace a stored value, such as `form_publish`, `formTheme_set` and `editor_updateElement`. Most clients ask before running them, but the prompt is the client's choice, so check its approval settings if you need a hard stop. ## Limitations A tool call cannot carry a file. Images are set by URL: covers, logos and image blocks take an `http(s)://` or `data:image` URL. A per-request document is the exception: `document_create` returns an upload URL that a client with HTTP access can `PUT` the file to, as in [Documents for one recipient](https://formstep.io/docs/developers/requests-api/#documents). To turn a PDF or a screenshot into a form, use the [built-in AI chat](https://formstep.io/docs/ai/using-ai/#files). ## Connecting with an API token Most clients sign in with OAuth: add the URL with no header and follow [Connect an AI agent](https://formstep.io/docs/guides/ai-agents/connect/), which also has the setup for Cursor. A client that cannot open a browser, such as a script, a CI job or a headless agent, sends an [API token](https://formstep.io/docs/developers/api-tokens/) as a header instead. Claude Code, from the command line: ```bash claude mcp add --transport http formstep https://api.formstep.io/api/mcp \ --header "Authorization: Bearer fs_YOUR_TOKEN" ``` Or in a project's `.mcp.json`. Other clients take the same URL and header; see their documentation for where. ```json { "mcpServers": { "formstep": { "type": "http", "url": "https://api.formstep.io/api/mcp", "headers": { "Authorization": "Bearer fs_YOUR_TOKEN" } } } } ``` ## Using OAuth instead of API tokens An app that connects on behalf of a user should use OAuth instead of asking for a pasted token. Formstep is an OAuth 2.1 authorization server with mandatory PKCE (S256) and opaque tokens: no JWTs and no implicit grant. Claude, Claude Code and other clients discover it all from the MCP endpoint, so the URL with no header is enough: the 401 points at `/.well-known/oauth-protected-resource`, and the client takes it from there. The flow, for a client you write yourself: 1. `GET /.well-known/oauth-protected-resource`, then `GET /.well-known/oauth-authorization-server` for the endpoint URLs, scopes and supported auth methods. 2. `POST /oauth/register` with your `redirect_uris` (dynamic client registration, no credentials needed). You get a `client_id`, plus a `client_secret` if you asked for anything other than `token_endpoint_auth_method: "none"`. Redirect URIs must be HTTPS, or HTTP on `localhost`. Registration is capped at 20 an hour per IP. 3. Send the user to `/oauth/authorize` with `response_type=code`, your `client_id`, the registered `redirect_uri`, `scope=mcp:read mcp:write offline_access`, `state`, and a `code_challenge` with `code_challenge_method=S256`. They sign in, pick one workspace, and authorize. 4. Exchange the code at `POST /oauth/token` with `grant_type=authorization_code` and your `code_verifier`, within 60 seconds. A code works once. 5. Call the MCP endpoint with `Authorization: Bearer fso_...`. Access tokens last 1 hour. Refresh tokens last 30 days and rotate on every use; reusing a spent refresh token revokes the whole chain, so always store the newest one. `POST /oauth/revoke` (RFC 7009) revokes an access or refresh token. ### Connected apps Every OAuth connection is listed under **Connected apps** on the **OAuth and API Keys** page, with when it was connected and last used. Connections are personal: only the user who authorized one sees it, and workspace admins cannot see or revoke another member's. Disconnecting revokes every token the app holds for that workspace at once. A user who leaves or is removed from the workspace loses all their connections to it. [Connect an AI agent](https://formstep.io/docs/guides/ai-agents/connect/) — Set up Formstep in any MCP client. [Create requests from code](https://formstep.io/docs/developers/requests-api/) — The request parameters an agent sends. For what an agent can do with it, see [the MCP server overview](https://formstep.io/integrations/mcp/). # Webhook API reference Payload schema, event types, signing, retries, and REST subscription endpoints. Read it to parse, verify, and dedupe the events Formstep sends to your URL. > **Looking for the setup guide?** > > To set up a custom webhook for your form in the app, see [Custom webhooks](https://formstep.io/docs/integrations/webhooks/). ## Request `POST ` with `Content-Type: application/json`. The URL must use HTTPS, carry no credentials, be at most 2,048 characters, and resolve to a public address. The address is checked again, DNS included, before every delivery. ### Headers - `Content-Type: application/json` - `X-Formstep-Signature: t={timestamp},sha256={hex}`, present when a signing secret is set up (see below) - `X-Formstep-Event-Id` and `X-Formstep-Event-Type`, which carry the same values as `id` and `type` in the body, so you can dedupe and route before parsing. A [request callback](https://formstep.io/docs/developers/callbacks/) sends the same two. - Up to 5 custom headers you add during setup. They are merged in as given, except `Content-Type`, which cannot be overridden. Native subscriptions created through `webhooks.create` have no custom headers. ## Event types ### Subscription events When you set up a webhook integration, you choose which event triggers deliveries: | Event | When it fires | | - | - | | submission\_created | A respondent completes and submits the form. This is the default. | | submission\_updated | A respondent edits a submission they already sent, when the form allows editing after submit. | | submission\_abandoned | A draft submission has been idle past the configured window. Requires partial submission tracking (Pro and Business). | | request\_completed | A recipient completes a request on the form. Carries the request block and the answers. | | request\_expired | A request on the form expires before the recipient completes it. Request block only. | | request\_canceled | A request on the form is canceled. Request block only. | The three `request_*` events are subscribed through `webhooks.create` and are what the Formstep apps for Zapier, Make and n8n listen on. Each delivers the same envelope a [request callback](https://formstep.io/docs/developers/callbacks/) sends, signed with the subscription’s own secret. Test requests never reach a subscription, and `requests.replayCallback` re-sends the callback only. Each subscription listens to one event. `submission_created` is share-link traffic and `request_completed` is request traffic, so a completed request never fires `submission_created` and a form with both subscriptions receives one delivery per completion. An edit reaches a `submission_updated` subscription alone, never a `submission_created` one. The webhook you add in the form’s Integrations dialog has no event choice: it receives first submissions and edits alike, told apart by `type`. ### Payload event types The `type` field in the JSON body tells you what happened: - `submission.completed`: a new completed submission - `submission.updated`: an existing submission was edited - `submission.abandoned`: a draft submission was abandoned after the configured idle window A [request callback](https://formstep.io/docs/developers/callbacks/) and a `request_*` subscription use the same envelope with `request.completed`, `request.expired` and `request.canceled`, so one parser reads all six. ## Payload shape ```json { "id": "evt_abc123", "type": "submission.completed", "createdAt": "2026-04-25T12:34:56.000Z", "apiVersion": "2026-09-24", "test": false, "data": { "form": { "id": "frm_...", "name": "Contact", "snapshotId": "snp_..." }, "submission": { "id": "sub_...", "respondentEmail": "alice@example.com", "submittedAt": "2026-04-25T12:34:56.000Z", "updatedAt": null, "editCount": 0, "pdfUrl": null, "language": "en" }, "answers": { "email": "alice@example.com", "plan": "pro", "attendees": [{ "attendee_name": "Grace Hopper" }, { "attendee_name": "Alan Turing" }] }, "display": { "email": "alice@example.com", "plan": "Pro", "attendees": "Grace Hopper, Alan Turing" } } } ``` | Key | What it is | | - | - | | id | The event id. Retries reuse it, so deduplicate on it. | | type | One of the six event types above. | | createdAt | When the event was queued, not when this delivery attempt was made. Stable across retries. | | apiVersion | The payload contract, as a date. It changes when a key is removed, renamed or changes meaning. New keys arrive without a change. | | test | true for a sample or test delivery, and for a request created in test mode. Always present. | | data.form.snapshotId | The published version the respondent answered. Field keys, titles and types are fixed per snapshot. | | data.submission.updatedAt | When the respondent last edited the submission, or null until the first edit. | | data.submission.editCount | How many times the respondent edited the submission after sending it: 0 on submission.completed, 1 on the first edit. | | data.answers | Every answer, keyed by field key. Each answer appears once. | | data.display | Human-readable text for every answer, under the same keys. | | data.schema | Optional: the field list (key, title, type, group, and the option or row and column keys with their labels), when the webhook was set up to send it. | | data.aiValidations | Present when the form has AI Validations: one entry per check under its key, with status (passed, flagged or not\_checked), the fields it read by field key, a reason when it flagged the answers, and a notCheckedReason (no\_plan or no\_credits) when the plan or the AI credits kept it from running. See AI Validation. | ### answers and display `answers` is the flat `{ field key: value }` object, keyed by the field keys frozen at publish. Read it when a workflow branches or stores a value: `answers.email`. A choice answer is the chosen option’s key (the `key` that `fields.list` lists for that option), so it is the same whatever language the respondent answered in. A date is an ISO string, a number a number, a multi-select an array of option keys. Unanswered fields are left out, never sent as `null`. `display` carries the same keys with human-readable text: the option label instead of its key, a formatted date, a joined list. Read it when a person will see the value, for example in a Slack message, a spreadsheet cell or an email. A repeating group appears in `answers` once, under the group’s own field key, as an array of row objects keyed by each member’s field key (`answers.attendees[0].attendee_name` above), and in `display` as one line with the rows joined. A member is never hoisted to the top level. [Calculated fields](https://formstep.io/docs/building-forms/calculated-fields/) appear in both maps under the calculated field’s name as its key (`answers.total`). ### Bookings and payments A Schedule appointment question and a Payment question each carry an object in `answers`, under the question’s field key, and one line of text in `display`. The times are ISO instants, so a spreadsheet or a workflow can parse them whatever language the respondent answered in: ```json { "book_a_call": { "status": "confirmed", "start": "2026-09-29T07:00:00.000Z", "end": "2026-09-29T07:30:00.000Z", "timeZone": "Europe/Oslo", "attendee": { "name": "Grace Hopper", "email": "grace@example.com" }, "meetingUrl": "https://app.cal.com/video/...", "provider": "cal.com", "providerBookingId": "...", "eventTitle": "Intro call" }, "pay_the_fee": { "status": "paid", "amount": 40, "currency": "USD", "amountRefunded": 0, "receiptUrl": "https://pay.stripe.com/receipts/...", "paidAt": "2026-09-24T10:12:00.000Z", "refundedAt": null, "disputedAt": null, "provider": "stripe", "providerPaymentIntentId": "pi_..." } } ``` A booking’s `status` is `confirmed`, `rescheduled`, `cancelled`, `rejected` or `no_show`. A payment’s is `paid`, `partially_refunded`, `refunded` or `disputed`, and `amount` is in the currency’s main unit: `40` is $40.00. When Cal.com moves a booking or Stripe refunds a payment after the submission, Formstep updates the object, so `submissions.list` and later events show the current state; no new event is sent for the change. Before `apiVersion` `2026-09-24`, a booking was sent as one sentence in `answers` and a payment was not sent at all. The webhook’s field mapping applies to both maps at once: choose “selected” fields and the others are left out; rename a field’s column and the new name is its key in `answers` and `display` alike. A field the form published before field keys existed goes out under its element id; publish the form again to give it a readable key. ### Field titles and types The event does not repeat each field’s title and type. Read them from `fields.list`, which is stable per `data.form.snapshotId`, so you can cache the field list and refetch only when the snapshot id changes. A receiver that cannot make a second call can turn on **Send the field list with every event** in the webhook’s settings; the event then carries `data.schema`, one entry per field. A choice question lists its `options` and a matrix its `rows` and `columns`, each as `{ key, label }`, so the keys in `answers` resolve to labels without a second call: ```json [ { "key": "email", "title": "Email", "type": "email", "group": null }, { "key": "plan", "title": "Plan", "type": "radio", "group": null, "options": [{ "key": "free", "label": "Free" }, { "key": "pro", "label": "Pro" }] }, { "key": "attendee_name", "title": "Attendee name", "type": "text", "group": "attendees" } ] ``` ### The request block On the [custom webhook](https://formstep.io/docs/integrations/webhooks/) added in the form’s Integrations dialog, a submission that answered a [request](https://formstep.io/docs/requests/overview/) carries one extra object inside `data`, `request`. It is absent on every public-link submission, so its presence is how that receiver tells the two channels apart. A Zapier, Make or n8n subscription never sees it: request traffic reaches a subscription as `request.completed`, which carries the full request block. ```json "request": { "id": "kd7...", "externalId": "run-42", "metadata": { "crm_record": "rec_88" } } ``` - `id`: the request this submission answered. Pass it to `requests.get` for the full picture. - `externalId` and `metadata`: your own bookkeeping, exactly as you supplied it on `requests.create`. Each is present only when it was set. > **Webhooks are not callbacks** > > A submission webhook fires on a submission; the request block only names the request it answered. A [callback](https://formstep.io/docs/developers/callbacks/) fires when a request ends (completed, expired, or canceled) and carries `context` and `outcome`. Expiry and cancellation have no submission, so no submission webhook ever fires for them. To hear a request end without a callback URL, subscribe to `request_completed`, `request_expired` or `request_canceled` through `webhooks.create`. ### AI Validation results Each [AI Validation](https://formstep.io/docs/building-forms/ai-validation/) check reports under its own key in `data.aiValidations`. A check is keyed `ai_validation`, `ai_validation_2` and so on, until someone gives it a name in the editor's Fields tab. Callbacks, `requests.get` and the MCP tool `formSubmission_list` carry the same object. ```json { "website_check": { "status": "passed", "fields": ["company_website"] }, "email_domain": { "status": "flagged", "reason": "The work email uses gmail.com, not the company's domain maersk.com.", "fields": ["work_email", "company_website"] } } ``` Branch on `status`: send a flagged submission to a person to review, or send the recipient a new request for the missing document. ### Documents blocks A [Documents block](https://formstep.io/docs/building-forms/documents-block/) records which files the respondent was shown, under the block's field key. `source` is `form` for a file the author uploaded and `request` for one a request added. A checkbox that confirms reading them answers under its own key. ```json { "documents": [ { "name": "Price list 2026", "source": "form" }, { "name": "Your lease contract", "source": "request" } ], "contract_read": true } ``` ### Submission PDF `data.submission.pdfUrl` is a link to the submission PDF. A form with a custom webhook or a Zapier, Make or n8n subscription keeps a PDF of every submission, so their events carry the link. It is `null` when the submission has no PDF, or when the PDF could not be rendered. ### File URLs `pdfUrl` and every file answer are `/api/storage/` URLs. The key in the path is random and unguessable, and anyone who holds the URL can download the file for as long as it exists: there is no sign-in, and the URL itself does not expire (each visit redirects to a signed download that lasts an hour). Treat these URLs as part of the submission data: store them where only your systems read them, keep them out of logs and tickets, and do not forward them by email or chat. Formstep follows the same rule: its notification emails attach the PDF rather than link to it. Deleting the submission deletes the file, and the URL then answers 404. ### Submission language `data.submission.language` is the BCP-47 code of the language the respondent submitted in (for translated forms). It is `null` for single-language forms. Use it to route or branch on the respondent’s language without a separate lookup. ## Abandoned submission payloads When you subscribe to `submission_abandoned`, Formstep checks for idle drafts every hour. If a draft has been inactive past the configured idle window, Formstep fires a delivery. Native API integrations must send `idleWindow` to `webhooks.create`. Accepted values are `12h`, `1d`, `3d`, and `1w`. There is no implicit default; an abandoned subscription without a value is rejected. | Idle window | Description | | - | - | | 12 hours | For same-day follow-ups | | 1 day | A reasonable gap before nudging | | 3 days | For less urgent forms | | 1 week | For low-frequency forms | The payload has the same shape as a completed submission, with two differences: - Answers may be sparse: only questions the respondent answered appear in `answers` and `display`. - `submittedAt` falls back to the dispatch timestamp, since the respondent never formally submitted. Each integration fires at most once per abandoned draft. After delivery, the draft is excluded from future sweeps. ## Signing Each webhook has its own signing secret. A custom webhook gets a `whsec_` secret when you set it up, which only the workspace owner can reveal later. A subscription gets one from the optional `signingSecret` parameter (32 to 255 characters) on `webhooks.create`. It is not the workspace request signing secret used for [request callbacks](https://formstep.io/docs/developers/callbacks/#verify), but the header and the algorithm are identical, so the verifier below handles both. Every signed delivery carries `X-Formstep-Signature: t={seconds},sha256={hex}`. The digest is an HMAC-SHA256 of `{t}.{raw body}`, keyed with your secret. Hash the raw body before any parsing or re-serializing, and compare in constant time. **Node.js** ```javascript import crypto from 'node:crypto' export function verifyFormstepSignature(rawBody, header, secret, toleranceSeconds = 300) { if (!header) return false const parts = Object.fromEntries(header.split(',').map((p) => p.split('=', 2))) if (!parts.t || !parts.sha256) return false const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex') const a = Buffer.from(expected, 'hex') const b = Buffer.from(parts.sha256, 'hex') if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return false return Math.abs(Date.now() / 1000 - Number(parts.t)) <= toleranceSeconds } ``` **Python** ```python import hashlib, hmac, time def verify_formstep_signature(raw_body: bytes, header: str, secret: str, tolerance_seconds: int = 300) -> bool: parts = dict(p.split('=', 1) for p in (header or '').split(',') if '=' in p) t, received = parts.get('t'), parts.get('sha256') if not t or not received: return False expected = hmac.new(secret.encode(), f'{t}.'.encode() + raw_body, hashlib.sha256).hexdigest() if not hmac.compare_digest(expected, received): return False return abs(time.time() - int(t)) <= tolerance_seconds ``` Formstep does not enforce a replay window, so the tolerance above is yours to choose. Changing the secret takes effect on the next attempt, including retries already in flight. Update your receiver first. > **Always verify in production** > > Without verification, anyone who discovers your URL can post fake submissions. ## Retries Answer with a 2xx within 10 seconds. A delivery gets up to 5 attempts, at least 1, 2, 4 and 8 minutes apart. Formstep looks for due retries every 30 minutes, so the last attempt comes about two hours after the first. A `Retry-After` header is honoured when it asks for longer than the next step. - A 429, a 5xx, a timeout or a reset connection is retried. - Any other 4xx stops at once: your endpoint refused the body, and sending it again cannot help. A 408 stops too, unlike for a [request callback](https://formstep.io/docs/developers/callbacks/#retries). - A destination that is blocked, does not resolve, or resolves to a private address is never retried. The URL is checked again, DNS included, before every attempt. A 401, 403 or 404 pauses the integration at once and emails the person who set it up. After 5 failed deliveries in a row, the integration pauses too. Fix the endpoint, then open Integrations on the form, pick the webhook under **On this form**, and click **Reactivate**. A successful delivery resets the count. Every attempt of one event carries the same `id` and `createdAt`, so deduplicate on `id`. An edit by the respondent is a separate event with its own `id` and `type: "submission.updated"`, sent to the form's custom webhook or a `submission_updated` subscription. `data.submission.editCount` counts the edits, and `data.submission.updatedAt` says when the latest one happened. ## Testing The integration setup and detail panel both have a **Send test** button. It fires a sample of the subscribed event to your URL so you can verify the connection without waiting for a real submission or request. A sample answer is made up per type, such as “John Doe” for text, 42 for a number and for an email, and it is signed and carries your custom headers. The same samples are available over the API as `submissions.sample` and `requests.sample`. Formstep only delivers to public HTTPS addresses, so `localhost` does not work. While you develop, give your machine a public address with a tunnel: ```bash # Cloudflare quick tunnel, no account needed cloudflared tunnel --url http://localhost:3000 # or ngrok ngrok http 3000 ``` > **Local development** > > Use the tunnel URL as the webhook URL and click Send test, or pass it as a request's `callbackUrl`. ## Next steps [Webhooks setup](https://formstep.io/docs/integrations/webhooks/) — Configure webhooks for your form [Plans & pricing](https://formstep.io/docs/subscription-billing/plans-pricing/) — Compare plan API features and limits --- ## Workspaces & Teams # Workspaces overview What a workspace holds, how the owner’s plan covers every member, and when to keep work in more than one workspace. ## What a workspace holds Every form belongs to one workspace, with its requests and submissions. The workspace also holds your folders, Trash, domains and the integration accounts the owner connected. Each member manages their own API keys under **OAuth and API Keys**. Only the owner opens **Workspace Settings**, from the workspace name at the top of the sidebar. There the owner renames the workspace, sets its timezone and the language of its notification emails, invites and removes members, and can delete the workspace with everything in it. ## The owner’s plan covers everyone A workspace gets the features of its owner’s plan, so members need no plan of their own and there are no per-seat fees. In a Pro workspace every member gets Pro features, and a Pro member in someone’s Free workspace gets Free features there. Uploads and answers count toward the owner’s storage and [monthly allowance](https://formstep.io/docs/subscription-billing/limits-quotas/). AI credits stay personal, unless a Business owner [shares theirs](https://formstep.io/docs/ai/ai-credits-usage/#shared-workspace-credits). ## More than one workspace Workspaces are unlimited on every plan. Keep one per client, team or brand when their forms, members and domains should stay apart. To add one, open the workspace switcher at the top of the sidebar and choose **Create workspace**; the same menu switches between them. Every workspace you own shares your storage and monthly allowance, so a new one does not raise your limits. ## Pages in this section - [Inviting members](https://formstep.io/docs/workspaces-teams/inviting-members/) — Add teammates to a workspace with an invite link, choose how long it works and how many people can use it, and see what the owner and members can do. - [Folders](https://formstep.io/docs/workspaces-teams/folders/) — Organize the forms in a workspace with folders. - [Trash & restore](https://formstep.io/docs/workspaces-teams/trash/) — Deleted forms move to Trash, where you can restore them or remove them for good. # Inviting members Add teammates to a workspace with an invite link, choose how long it works and how many people can use it, and see what the owner and members can do. ## Create an invite link 1. **Open Workspace Settings** Click your workspace name at the top of the sidebar, then choose Workspace Settings. 2. **Click Invite** Under Members, click Invite. Formstep creates a link if there is none. 3. **Limit the link if you like** Turn on Set expiration date or Limit uses. A limit of 1 makes a link for one person. 4. **Copy and share it** Send it by email, chat or anywhere else. The person who opens the link sees the workspace name, signs in or creates a free account, and joins. A workspace has one invite link at a time: **Replace this link** makes a new one and stops the old one, and **Delete this link** stops it. People who already joined stay. ## What the owner and members can do A workspace has one owner and any number of members. Members create, edit, publish and delete forms, send and manage requests, read and export submissions, add integrations to forms with the accounts the owner connected, and organize folders and themes. There is no read-only role. Only the owner can open Workspace Settings, Domains and the workspace’s integration accounts, manage the invite link and members, share AI credits, see billing, and delete the workspace. The owner cannot leave, and cannot hand the workspace over in the app; for that, email [support](https://formstep.io/docs/getting-started/support/). Each member’s API keys are their own, under **OAuth and API Keys**, and stop working when the member leaves or is removed. For what a member’s plan and AI credits come to, see [the owner’s plan covers everyone](https://formstep.io/docs/workspaces-teams/overview/#plans). ## Removing a member In **Workspace Settings**, under **Members**, choose **Remove member** on their row. They lose access at once. The forms they made stay in the workspace. ## Leaving a workspace A member can leave from the workspace switcher with **Leave workspace**. To come back, they need a new invite. ## Next steps [Folders](https://formstep.io/docs/workspaces-teams/folders/) — Organize the forms in a workspace [Workspaces overview](https://formstep.io/docs/workspaces-teams/overview/) — What a workspace holds and whose plan applies # Folders Organize the forms in a workspace with folders. Create, nest, rename and delete them, move forms between them, and find any form by name. ## How folders work A form is in one folder, or in none. Folders can hold other folders. ## Creating a folder On the Forms page, click **+ Folder** and name it. A folder or form you create while inside a folder goes into that folder. ## Finding a form The search box on the Forms page finds forms and folders by name in the folder you have open. To search the whole workspace, use **Search** in the sidebar. ## Moving forms and folders 1. **Choose what to move** For one form or folder, open its **⋯** menu and click **Move to folder**. For several, check their boxes and click **Move** in the toolbar. 2. **Pick a destination** Click a folder in the tree, or type its name in the search box. **Home** takes them out of every folder, and **New folder** creates one inside the folder you picked. 3. **Click Move** The button names the folder you picked. ![The Move dialog for one form: a folder search, Home marked Current, Sales & Events opened with Webinars picked, and the Move to Webinars button](https://formstep.io/docs/images/workspaces-teams/move-dialog.png) Open a folder with its arrow and pick where the form goes. The button names the folder. ## Renaming a folder Open the folder’s **⋯** menu and click **Rename**. ## Deleting a folder Open the folder’s **⋯** menu and click **Delete**. This deletes the folder and the folders inside it. Their forms move to [Trash](https://formstep.io/docs/workspaces-teams/trash/), where you can restore them for 30 days. ## Folders and AI AI chat and your own AI tool can create, rename and move folders, and move forms. AI chat asks before it deletes a folder. See [Using AI chat](https://formstep.io/docs/ai/using-ai/#manage). ## Next steps [Trash & restore](https://formstep.io/docs/workspaces-teams/trash/) — Get a deleted form back [Workspaces overview](https://formstep.io/docs/workspaces-teams/overview/) — What a workspace holds # Trash & restore Deleted forms move to Trash, where you can restore them or remove them for good. Forms stay there for 30 days, and share links stay revoked after a restore. ## What happens when you delete a form The form moves to **Trash** with its submissions and analytics. Formstep also: - revokes its share links, so links and embeds stop working; - cancels its pending requests, and tells the workflow that sent them. Restoring the form does not bring them back. A form in Trash cannot be edited or published. > **Deleted folders** > > Deleting a [folder](https://formstep.io/docs/workspaces-teams/folders/#delete) moves its forms here. ## Opening Trash Click **Trash** in the sidebar. Each deleted form shows the folder it was in, if any, when it was deleted and by whom, and how many days it has left. ![The Trash page with three deleted forms, each with the folder it was in, when it was deleted, the days it has left, who deleted it, and Restore](https://formstep.io/docs/images/workspaces-teams/trash.png) Each form in Trash, with the folder it came from and the days it has left. ## Restoring a form Click **Restore** on the form, or check several forms and click **Restore** in the toolbar. The dialog picks the folder the form was in; pick another folder or **Home** if you like, then click **Restore to …**. The form comes back with its submissions. Its share links stay revoked: open **Public link** and click **Re-activate** on each link you want back. ![The Restore dialog for one form, with Webinars, the folder it was in, already picked and the Restore to Webinars button](https://formstep.io/docs/images/workspaces-teams/restore-dialog.png) Restore starts from the folder the form was in. ## Deleting permanently **Delete forever**, on the form’s row or in the toolbar for the forms you checked, removes the form with its submissions, analytics, and files. **Empty trash** does this for every form in Trash. Forms in Trash count toward your storage until they are gone. > **Permanent deletion is irreversible** > > Export anything you want to keep first. ## Automatic cleanup Forms are deleted for good after 30 days in Trash. On Free, a form that has been idle for a year also moves here on its own, after a warning email; see [idle forms](https://formstep.io/docs/subscription-billing/limits-quotas/#inactive-forms). ## Next steps [Publish history](https://formstep.io/docs/building-forms/version-history/) — Browse and restore past versions of a form [Folders](https://formstep.io/docs/workspaces-teams/folders/) — Organize the forms in a workspace [Exporting submissions](https://formstep.io/docs/submissions-analytics/exports/) — Download submissions before deleting [Submission retention](https://formstep.io/docs/submissions-analytics/submission-retention/) — How long Formstep keeps response data --- ## Subscription & Billing # Subscription & Billing overview Formstep has a Free, a Pro and a Business plan. What sets them apart, whose plan a workspace uses, and how AI credits fit in. ## The plans Free is the whole product at a smaller scale, with no credit card. Pro is for working under your own name, with your own domains and emails to your recipients. Business is for teams with compliance needs, with extras such as respondent email verification and data retention. The plan that counts is the workspace owner’s, so members never pay for a seat. ## Limits and credits Each plan sets a monthly allowance of submissions and requests, and a storage limit. AI is separate: it runs on credits you buy in packs, on any plan. ## Pages in this section - [Plans & pricing](https://formstep.io/docs/subscription-billing/plans-pricing/) — Start free with no credit card, and move to Pro or Business when you need more. - [Limits & quotas](https://formstep.io/docs/subscription-billing/limits-quotas/) — Every limit on the Free, Pro and Business plans: the monthly allowance, storage, domains, free invitations, idle forms, and what happens when you reach one. - [Billing & invoices](https://formstep.io/docs/subscription-billing/billing-invoices/) — Update your payment method, download invoices and cancel in the Polar billing portal, and see what a failed payment means. - [Upgrading & downgrading](https://formstep.io/docs/subscription-billing/upgrading-downgrading/) — What happens when you upgrade, move down a plan, cancel, or miss a payment: the grace period, what changes on Free, and what ends on a schedule. # Plans & pricing Start free with no credit card, and move to Pro or Business when you need more. What each plan is for, and how AI credits are paid for. ## What each plan is for - Free is the whole product at a smaller scale, on Formstep links with Formstep branding. - Pro is for collecting at volume under your own name: your own domains, no Formstep branding, and invitation and reminder emails for requests. - Business is for teams with compliance needs: more storage, respondent email verification, data retention and going back to an earlier version of a form. Current prices in your currency, the annual discount and every feature plan by plan are on the [pricing page](https://formstep.io/pricing/). The numbers behind each plan are in [Limits & quotas](https://formstep.io/docs/subscription-billing/limits-quotas/#plan-limits). A paid plan covers the whole team: when the workspace owner is on Pro or Business, every member gets its features. See [the owner’s plan covers everyone](https://formstep.io/docs/workspaces-teams/overview/#plans). ## AI credits AI chat, translating a submission and AI Validation run on credits. No plan includes credits: on every plan you buy packs, at the same price, and they never expire. Buy them on the **AI Usage** page, from the credits row at the bottom of the sidebar. See [AI credits & usage](https://formstep.io/docs/ai/ai-credits-usage/). ## Paying Start a plan from the **Subscription** page. Formstep bills through Polar, and you manage your payment method and invoices there. See [Billing & invoices](https://formstep.io/docs/subscription-billing/billing-invoices/) and [Upgrading & downgrading](https://formstep.io/docs/subscription-billing/upgrading-downgrading/). ## Next steps [Limits & quotas](https://formstep.io/docs/subscription-billing/limits-quotas/) — Every number, plan by plan [Upgrading & downgrading](https://formstep.io/docs/subscription-billing/upgrading-downgrading/) — Switch plan, change cycle or cancel # Limits & quotas Every limit on the Free, Pro and Business plans: the monthly allowance, storage, domains, free invitations, idle forms, and what happens when you reach one. ## Plan limits Prices, and the features each plan includes, are on the [pricing page](https://formstep.io/pricing/). | Limit | Free | Pro | Business | | - | - | - | - | | Monthly allowance (submissions & requests) | 1,000 | 50,000 | 50,000 | | Storage | 100 MB | 100 GB | 500 GB | | Forms, workspaces and members | Unlimited | Unlimited | Unlimited | | Your own domains (form and email domains together) | None | Up to 5 | Up to 20 | | Form emails per workspace per day | 500 | 10,000 | 25,000 | | Analytics history | Not collected | 1 year | 1 year | | Request invitation emails | 10 free, to your own address | Included | Included | | Sending one form to a list of people | None | Up to 500 people per send | Up to 500 people per send | | Versions you can go back to | None | None | Last 50 per form | | Idle forms | Moved to Trash after a year | Never moved | Never moved | > **Going above Business** > > On Business, storage and the monthly allowance can be raised as a paid add-on. Email [support](mailto:support@formstep.io) with how much you need, and we agree a price and add it to your account. ## The monthly allowance One allowance covers both ways of collecting answers. One unit is spent when: - someone completes a submission through a share link, or - you create a request, answered or not, because the unit pays for its invitation and reminders. The submission a request collects is already paid for, so it spends nothing. Test requests, unfinished drafts and abandoned forms spend nothing either. The allowance belongs to the workspace owner and resets on the 1st of each month (UTC). Follow it on the **Subscription** page, under **Plan usage**. ## Free invitations On Free, Formstep emails 10 request invitations for you, to your own address only, so you can see what your recipients would get. They belong to the workspace owner’s email address, never come back, and cover the invitation only, with no reminders. An invitation that bounces still counts. The New request sheet shows how many are left. After that, you can still create requests and send each link yourself, to anyone. ## Idle forms on Free On Free, a form with no edits and no submissions for more than a year moves to Trash, at least 30 days after a warning email to the owner. If that email cannot be delivered, the form stays. In Trash you have 30 days to restore it before it is deleted with its submissions and files. An edit, a submission or a restore starts the year again. Pro and Business forms are never moved. ## Limits in a workspace Storage and the monthly allowance belong to the workspace owner’s plan. See [the owner’s plan covers everyone](https://formstep.io/docs/workspaces-teams/overview/#plans). ## When you reach a limit Nothing you have collected is deleted, and your forms stay published. Only new activity on that limit pauses, and it starts again as soon as you free space, the month resets, or you upgrade. - Monthly allowance: a share link shows _This form is closed_ as soon as someone opens it, before they fill anything in. If the allowance runs out while someone is answering, their submit is refused with _This form can’t accept responses right now_, and their answers are not saved. You cannot create new requests, but requests you already created can still be answered. - Storage: new file uploads fail, and a form that keeps a PDF of each submission, such as one with a signature, shows as closed. Existing files stay downloadable. Delete old submissions or files, or upgrade. - Domains: the domains you have keep working. To add another, remove one or upgrade. Formstep emails the workspace owner at 90% and again at 100% of storage or the monthly allowance, at most once a month for each. ## Next steps [Plans & pricing](https://formstep.io/docs/subscription-billing/plans-pricing/) — What each plan is for [Upgrading & downgrading](https://formstep.io/docs/subscription-billing/upgrading-downgrading/) — What changes when you switch plan or a payment fails [Billing & invoices](https://formstep.io/docs/subscription-billing/billing-invoices/) — Payments and invoices [AI credits & usage](https://formstep.io/docs/ai/ai-credits-usage/) — How AI use is paid for # Billing & invoices Update your payment method, download invoices and cancel in the Polar billing portal, and see what a failed payment means. ## The billing portal Open the **Subscription** page and click **Manage subscription**. The Polar billing portal opens, where you can download invoices, update your payment method, and cancel or reactivate your subscription. Polar keeps your card details; Formstep never sees your card number. The **Subscription** page itself is where you start a plan, move between Pro and Business, switch between monthly and annual, and follow your usage under **Plan usage**. See [switching billing cycle](https://formstep.io/docs/subscription-billing/upgrading-downgrading/#switching-billing). ## Invoices Polar makes an invoice for every payment: a new plan, a renewal, a plan change and each AI credit pack. Download them from the billing portal. Prices exclude tax, and you pay in your local currency where Formstep offers one. ## AI credit packs Credit packs are one-time purchases on the **AI Usage** page, and each shows as its own invoice. See [AI credits & usage](https://formstep.io/docs/ai/ai-credits-usage/). ## Failed payments If a payment fails, Polar tries again, and your plan keeps working for a [grace period](https://formstep.io/docs/subscription-billing/upgrading-downgrading/#grace-period). Update your payment method in the billing portal and everything is back at once. ## Cancelling Cancel in the billing portal. You keep your plan until the end of the period you paid for. See [what happens after you cancel](https://formstep.io/docs/subscription-billing/upgrading-downgrading/#cancelling). ## Next steps [Upgrading & downgrading](https://formstep.io/docs/subscription-billing/upgrading-downgrading/) — What changes when you switch plan [Legal overview](https://formstep.io/docs/legal/overview/) — Terms, privacy policy and data processing agreement # Upgrading & downgrading What happens when you upgrade, move down a plan, cancel, or miss a payment: the grace period, what changes on Free, and what ends on a schedule. ## Upgrading Open the **Subscription** page, click **Upgrade**, and pick Pro or Business, monthly or annual. The plan starts when checkout completes, for every workspace you own and every member in them. If you picked a plan on the Formstep website before you had an account, you land on it after signing in. On Pro, **Upgrade to Business** changes your subscription in place for the prorated difference. Upgrading again after a lapse, even months later, brings back your settings and lifts every restriction at once. ## Cancelling On the **Subscription** page, click **Manage subscription** and cancel in the billing portal. You keep your plan until the end of the period you paid for, and then Free limits apply at once. Deleting your account also cancels the subscription. ## Grace period after a failed payment When a payment fails, you get time to fix the card. Emails along the way tell you the dates. | When | What happens | | - | - | | Days 0 to 6 | Everything works as before, and a banner asks you to update your payment method. | | From day 7 | Paid extras such as removing Formstep branding pause, and you cannot add anything that needs a paid plan. Your forms keep collecting answers and files at your paid limits. | | From day 30 | Free limits apply everywhere. | | Around day 60 | Your own domains stop serving forms. | Update your payment method in the billing portal and the plan is back at once. The grace period is only for failed payments. A cancelled plan moves to Free when its paid period ends. A refund ends paid access on the day of the refund, and your own domains stop about three days later. ## What changes on Free - The monthly allowance and storage drop to the [Free limits](https://formstep.io/docs/subscription-billing/limits-quotas/#plan-limits). Once the allowance is spent, a share link shows _This form is closed_ as soon as someone opens it, and requests you already sent stay open. See [when you reach a limit](https://formstep.io/docs/subscription-billing/limits-quotas/#hitting-limits). - Forms show Formstep branding again. - The owner still gets an email for each submission. Confirmations to respondents, extra recipients, the PDF attachment, custom templates and your own email domain pause. - Unfinished drafts, request reminders and [sending to a list](https://formstep.io/docs/requests/bulk-send/) stop. Saved drafts come back when you upgrade. - The Analytics tab locks, and going back to an earlier version needs Business. - Forms keep their themes, but editing colors and fonts, and saving workspace themes, waits until you upgrade. - Bot protection is always on. On your own domain without your own Turnstile keys, that blocks submissions there; the formstep.io link keeps working. See [bot protection](https://formstep.io/docs/sharing-publishing/captcha-bot-protection/). - Your AI credits stay and keep working. ## Moving from Business to Pro Click **Switch to Pro** on the **Subscription** page. The change waits until the end of your billing period, and **Keep current plan** cancels it before then. When it applies, storage drops to the [Pro limit](https://formstep.io/docs/subscription-billing/limits-quotas/#plan-limits) and any extra allowance ends, so check **Plan usage** first. Respondents are no longer asked to verify their email, shared AI credits stop, and you cannot go back to an earlier version or set a new retention schedule. Domains you already have keep working. ## What ends on its own schedule - When your own domains stop serving, share links move back to formstep.io addresses, and printed QR codes with your domain stop working. Upgrade before the date in your emails and nothing stops. - Analytics stop recording on Free. History from your paid time stays for its usual year. - Below Business, older published versions that nothing uses any more go the next time you publish the form. See [version history](https://formstep.io/docs/building-forms/version-history/). - On Free, [idle forms](https://formstep.io/docs/subscription-billing/limits-quotas/#inactive-forms) move to Trash after a warning email. - A [retention schedule](https://formstep.io/docs/submissions-analytics/submission-retention/) you set keeps deleting old submissions, because it is a promise to your respondents. ## Switching between monthly and annual **Switch to annual**, on the **Subscription** page, applies at once: the unused part of your month counts toward the annual price, which is ten months’ price for twelve. **Switch to monthly** starts when the paid year ends, and **Keep current plan** cancels it before then. Neither works on a cancelled subscription. ## Next steps [Limits & quotas](https://formstep.io/docs/subscription-billing/limits-quotas/) — Every number, plan by plan [Billing & invoices](https://formstep.io/docs/subscription-billing/billing-invoices/) — Payment method and invoices --- ## Legal # Legal overview Index of Formstep legal documents: the Terms of Service, Privacy Policy, Data Processing Agreement, and Partner Terms, with governing law and contact details. ## Documents The documents below form the legal framework for using Formstep. The Partner Terms apply only to partners in the affiliate program. - [Terms of Service](https://formstep.io/docs/legal/terms-of-service/) — Terms governing your use of Formstep, its website, apps, and APIs: accounts, acceptable use, plans and billing, AI credits, your content, and liability. - [Privacy Policy](https://formstep.io/docs/legal/privacy-policy/) — How Formstep collects, uses, and protects personal data, for the people who build forms and the respondents who fill them in, and the rights you have. - [Data Processing Agreement](https://formstep.io/docs/legal/dpa/) — How Formstep processes personal data on your behalf under the GDPR and similar laws, with each party’s obligations, subprocessors, and security measures. - [Partner Terms](https://formstep.io/docs/legal/partner-terms/) — Terms for partners in the Formstep affiliate program: how referrals are credited, how commission and payouts work, and how you may promote Formstep. ## Who operates Formstep The Service is operated by **Formbase AS**, a Norwegian limited company with organisation number 937 921 217 and registered office in Oslo, Norway. ## Contact For general, legal, and security questions, write to . ## Governing law Norwegian law governs the Terms of Service and the Data Processing Agreement. The parties agree that the Oslo District Court (Oslo tingrett) has jurisdiction to settle any dispute, subject to the mandatory consumer protections described in the Terms of Service. ## Changes When we make a material change to any of these documents, we update the “Last updated” date at the top of that document and notify account holders by email or in-product notice at least thirty (30) days before it takes effect. Last updated: 23 May 2026 # Terms of Service Terms governing your use of Formstep, its website, apps, and APIs: accounts, acceptable use, plans and billing, AI credits, your content, and liability. > **Last updated: 12 November 2026** > > These Terms apply to everyone who creates an account or uses Formstep. Read them carefully. If you have questions, contact us at . ## 1. Acceptance of Terms These Terms of Service (“Terms”) govern your access to and use of Formstep, operated by Formbase AS, a Norwegian limited company with organisation number 937 921 217 and registered office in Oslo, Norway (“Formstep”, “we”, “us”), including our website, applications, APIs, integrations, and related services (collectively, the “Service”). By creating an account or accessing the Service, you agree to be bound by these Terms. If you are using the Service on behalf of an organisation, you represent that you have authority to bind that organisation to these Terms, and “you” refers to both you personally and that organisation. If you do not agree to these Terms, you must not create an account or use the Service. Your continued use of the Service following any updates to these Terms constitutes acceptance of the revised version. These Terms incorporate by reference our Privacy Policy, Data Processing Agreement (where applicable), and any product-specific terms presented to you within the Service. ## 2. Definitions For the purposes of these Terms, capitalized terms have the meanings set out below: - **Service** means the Formstep platform and all features made available through it, including the form builder, hosted forms, dashboards, integrations, AI features, and APIs. - **User** means any individual who accesses or uses the Service in an Account capacity, whether as a workspace owner or member. Respondents are not Users — see [Section 6](#acceptable-use). - **Customer** means a User who holds the Formstep Account under which the Service is used; for individual accounts this is you, for business accounts this is the organisation you act on behalf of. - **Account** means the personal record created when you register for the Service, identified by a unique email address. - **Workspace** means a separately addressable environment within the Service in which forms, submissions, members, and settings are organised. - **Form** means any survey, questionnaire, application, checkout, or other interactive document created and published through the Service. - **Submission** means a response, entry, file upload, payment, or other data record collected through a Form. - **Subscription** means a recurring paid plan that unlocks features or capacity on your Account. - **Content** means any text, media, file, configuration, response, code, prompt, or other material that you, your collaborators, or your respondents create, upload, transmit, or store through the Service. - **Your Content** means the Content that belongs to your account, as described in [Section 8](#user-content). - **AI Features** means any functionality of the Service that uses machine learning or large language models to generate, transform, summarize, route, or otherwise process Content on your behalf. - **Respondent** means a person who submits a response to a Form, whether or not they hold an Account. ## 3. Eligibility To use the Service, you must be at least thirteen (13) years old and have the legal capacity to enter into a binding contract under the laws of your jurisdiction. Where the law that applies to you sets a higher minimum age for consenting to online services or to the processing of your personal data, you must meet that higher age. If you are below the age of majority in your country, you confirm that a parent or legal guardian has authorised your use of the Service. You may not use the Service if you are barred from doing so under applicable laws, including export controls and sanctions administered by Norway, the European Union, the United Nations, the United Kingdom, or the United States. You confirm that you are not located in, ordinarily resident in, or organised under the laws of a country or territory subject to comprehensive sanctions, and that you are not listed on any restricted-party or denied-persons list. We may, at our discretion, refuse, suspend, or terminate access where we reasonably believe these eligibility requirements are not met. ## 4. Account Registration and Security To access most features of the Service, you must register an Account. When you register, you agree to provide accurate, current, and complete information, and to keep that information up to date. Each Account is intended for a single human individual. You may not share login credentials, transfer your Account to another person, or allow multiple people to operate under the same Account (including through a shared inbox or alias). For team use, each team member must register their own Account and you may invite them to your Workspace under their own Accounts. You are responsible for safeguarding your credentials and for any actions taken under your Account, whether or not authorised by you. The Service does not use passwords of its own; you sign in through a magic link sent to your email address or through Google OAuth. You may also try Formstep without registering through a temporary anonymous guest session; guest sessions are temporary and do not persist data. Notify Formstep without undue delay if you become aware of any unauthorised access, suspected compromise, or other security incident involving your Account; you can also reach us at . To the extent permitted by applicable law, we are not liable for losses resulting from your failure to protect your credentials or to notify us promptly. ## 5. Workspaces and Team Accounts The Service is organised around Workspaces. The individual who creates a Workspace is its initial owner and is treated as the primary account holder for ownership of Content and administrative decisions affecting that Workspace. Workspace owners may invite additional members to a Workspace. Workspace roles are currently limited to owner and member; owners administer the Workspace and its billing-relevant configuration, and members can access forms, submissions, and integrations to the extent permitted by the Workspace’s settings, as described in the Service’s documentation and as updated from time to time. If you are invited to a Workspace, you acknowledge that the Workspace owner controls the configuration of that Workspace and may have visibility into, and authority over, the Forms, Submissions, and activity occurring within it. Your use of a Workspace is additionally subject to any internal policies set by its owner, provided they do not conflict with these Terms. A Workspace is intended to support a single team or organisation collaborating together. The paid features that extend to the members of a Pro-owned Workspace are provided for that collaboration. They are not a means to give the paid features of the Service to unrelated individuals, to multiple distinct organisations, or to the general public under a single Subscription. Formstep may apply fair-use limits to the number of members in a Workspace and reserves the right to require separate Subscriptions where a single Workspace, or a set of Workspaces under one Account, is used to extend paid features beyond a single team or organisation. Workspace ownership does not currently transfer through a self-service flow. Formstep is not a party to any agreement between Workspace owners and their members or Respondents, and is not responsible for resolving disputes among them. ## 6. Acceptable Use Policy You may use Formstep to build, publish, and manage forms for lawful purposes that comply with these Terms and any laws applicable to you and to the people you collect information from. Typical permitted uses include customer feedback, lead capture, surveys, registrations, applications, internal workflows, payment collection through supported processors, and similar form-driven activities. You are solely responsible for the forms you publish, the questions you ask, the data you collect, the files respondents upload, and how you store, share, or act on that information. If you collect personal data, you act as the data controller for that data and must provide respondents with any notices, choices, and legal bases required under applicable privacy laws. Formstep is a general-purpose form builder and is **not designed, certified, or warranted for use with regulated sensitive data**. Formstep does not currently offer any certification, business associate agreement, or written authorisation for regulated data use cases — no such arrangement can be obtained by email request alone. Unless we have explicitly agreed otherwise with you in a separate written agreement signed by an authorised representative of Formstep, you must not use Formstep to collect, store, or process: - Protected Health Information (PHI) subject to the U.S. Health Insurance Portability and Accountability Act (HIPAA) or equivalent health-data regimes; - Cardholder data subject to PCI DSS, except through the Payment field — Stripe collects and processes all card data directly; Formstep receives only non-sensitive references such as the last four digits; - Government-issued identifiers used as primary identity proofing (such as full social security numbers, national ID images) unless you have implemented your own additional safeguards; - Information about minors below the age of consent under applicable law, including data subject to the U.S. Children’s Online Privacy Protection Act (COPPA), unless you have obtained verified parental or guardian consent and have your own compliant workflow; - Any other category of data for which your applicable laws require a specific certification, contract, or technical measure that Formstep has not undertaken in writing. If your use case involves any of the above, you are responsible for assessing fitness yourself, putting any additional safeguards in place, and seeking written confirmation from Formstep before proceeding. Respondents are not Users of Formstep. Your relationship with respondents is governed by your own terms and privacy practices. You are responsible for handling their data and any disputes that arise from your forms. Electronic signatures collected through Formstep’s Signature field are not qualified or advanced electronic signatures under the EU eIDAS Regulation. They may not satisfy the heightened evidentiary requirements for document types that require a qualified signature under applicable law. You are responsible for assessing whether signatures collected through Formstep meet the legal requirements for your specific use case and jurisdiction. ## 7. Prohibited Uses You must not use Formstep, and must not allow anyone else to use your account, to do any of the following: - Publish, transmit, or solicit content that is illegal under the laws that apply to you or to your respondents. - Harass, threaten, defame, or incite violence against any person or group. - Send spam, unsolicited bulk messages, or use Formstep to drive traffic to deceptive destinations. - Conduct phishing, credential harvesting, or any form of social engineering. - Distribute malware, viruses, ransomware, or links to malicious software. - Scrape, crawl, or harvest forms, submissions, respondent data, or other data from Formstep’s applications or its users by automated means, except through documented APIs within published rate limits. This does not restrict reading, indexing, or linking to Formstep’s public website and documentation, by people or by automated tools, as `robots.txt` permits. - Circumvent or attempt to circumvent submission limits, plan restrictions, paywalls, rate limits, branding restrictions, or other technical controls. - Infringe any copyright, trademark, patent, trade secret, publicity, or other intellectual property right. - Upload, request, or generate child sexual abuse material (CSAM) or content that sexually exploits or endangers minors. Where we become aware of such content we are required by law to report it to the relevant authorities (for users in Norway, Kripos; we may also forward reports to organizations such as NCMEC (CyberTipline) where appropriate) and we will do so. - Commit fraud, run deceptive schemes, or misrepresent your identity, affiliation, or the purpose of your forms. - Create fake accounts, abuse guest sessions, use multiple accounts or sessions to evade limits or bans, or sell, rent, or transfer accounts to others. - Share, resell, or redistribute the paid features of the Service in breach of the Workspace fair-use rules in [Section 5](#workspaces). - Use Formstep AI Features to generate, refine, or distribute content that is illegal, harmful, hateful, sexually explicit involving minors, or that would otherwise violate these Terms. - Abuse payment-enabled forms, including processing test or stolen card data, laundering funds, or collecting payments for goods or services you cannot lawfully provide. - Interfere with the integrity, security, or performance of Formstep, including by probing, load testing without permission, or attempting to gain unauthorised access to any account, system, or data. We may update the list of prohibited uses as new patterns of abuse emerge. ## 8. Your Content and Ownership You retain all rights you already have in the content you bring to Formstep. This includes the forms you build, the questions and copy you write, the media and files you upload, the submissions your respondents send you, and any data you import or sync through integrations (“Your Content”). To operate the Service for you, you grant Formstep a worldwide, non-exclusive, royalty-free licence to host, store, copy, transmit, display, render, process, back up, and otherwise use Your Content solely as needed to provide, secure, and support the Service for your account, and to improve the Service through aggregated and de-identified analytics that do not contain personal data. This licence covers the technical operations required to run Formstep, such as serving forms to respondents, delivering email and webhook notifications, running integrations you enable, generating previews and analytics for you, and maintaining backups. If you use AI Features, you grant the additional permissions needed for those features to function, including sending the relevant inputs to the AI provider that powers AI Features on your behalf. The provider details, data-handling, and no-training commitments for AI Features are described in [Section 16](#ai). This licence lasts only as long as Your Content is on Formstep, and ends when you or we delete it, subject to short retention windows in backups and logs and to any obligation we have to retain records under applicable law. You are responsible for ensuring you have the rights needed to grant this licence for everything you upload, including any third-party content and any personal data of respondents. Formstep, the Formstep brand, the Service, its software, and all related materials we provide remain our property or the property of our licensors. Nothing in these Terms transfers ownership of the Service to you. ## 9. Content Moderation and Reporting We have the right, but not the obligation, to review content on Formstep. We do not pre-screen forms or submissions, and you should not rely on us to do so. When we become aware of content or behaviour that violates these Terms or applicable law, we may take any action we consider appropriate. This includes removing or disabling forms, files, or submissions, suspending or terminating accounts, withholding payouts on payment forms, preserving records for investigations, and reporting illegal content to the relevant authorities. Where reasonable and lawful, we will tell you what we have done and why. If you believe content on Formstep violates these Terms or your rights, contact us at with enough information for us to locate and assess it. For copyright and other intellectual property complaints, including notices similar to those provided for under the U.S. Digital Millennium Copyright Act, send your notice to and include the works concerned, the URLs of the allegedly infringing content, your contact details, and a statement that your complaint is made in good faith. Counter-notices may be sent to the same address. We may keep records of reports and the actions we take in response. Submitting a knowingly false or abusive report is itself a breach of these Terms. In appropriate circumstances, we terminate the accounts of users who repeatedly infringe the intellectual property rights of others. Formstep does not scan files uploaded by respondents for malware or other harmful content. You are responsible for any files your respondents upload through your forms, and you should not download or open files from untrusted respondents without appropriate precautions. ## 10. Plans and Subscriptions Formstep is offered on a free tier and paid Pro and Business tiers. Each plan defines the features and usage limits available to you, and the current details for every plan are published on the [Formstep pricing page](https://formstep.io/docs/subscription-billing/plans-pricing/). The free tier lets you build, publish, and collect responses from forms within the limits described on the pricing page. It is intended for personal use, evaluation, and low-volume projects. Formstep may apply soft caps, Formstep branding on public forms, or feature restrictions to the free tier, and may adjust these from time to time. If you exceed the limits applicable to your plan, Formstep may stop accepting new submissions on affected forms, display a limit-reached notice, or notify you by email. The Pro tier unlocks higher limits, custom domains, custom themes, and advanced analytics. No plan includes AI credits; you purchase them separately as described in [Section 12](#ai-credits). The Business tier builds on Pro and additionally includes respondent email verification, custom data retention schedules, version history, a higher custom-domain allowance, and an optional shared AI credit pool for workspace members. Third-party integrations (webhooks, automation platforms, and similar connections) are available on all plans; Pro unlocks higher submission volumes that make them more practical at scale. You may subscribe to Pro or Business on a monthly or yearly billing cycle. Yearly subscriptions are billed up front for the full term and typically include a discount relative to the monthly price. Subscriptions are tied to your individual Formstep user account, not to a workspace. If you belong to multiple workspaces, your plan entitlements and AI credit balance follow you across all of them. Workspaces themselves are not billed; only users are. When you are the owner of a workspace and your account is on a paid tier, that tier’s features (such as custom domains, higher submission limits, and advanced analytics) extend to all members of that workspace; members do not need their own subscription to access those features within your workspace. This member benefit is subject to the fair-use rules in [Section 5](#workspaces). On the free and Pro tiers, AI credits remain personal to each user. On the Business tier, the workspace owner may enable a shared pool, in which case members consume the owner’s purchased AI credits while working in that owner’s workspaces. Your subscription renews automatically at the end of each billing cycle using the payment method on file, unless you cancel before the renewal date. Formstep may change the prices, features, or limits of any plan. For changes that affect the price you pay on renewal, or that materially reduce the features or limits available on a paid plan, we will give you reasonable advance notice before the change takes effect. If you do not accept the change, you may cancel your subscription before it renews, and your access will continue under your existing plan until the end of your current billing period. ## 11. Payment Processing and Billing All subscription payments and AI credit purchases on Formstep are processed by Polar (polar.sh), our payment provider. Polar acts as the Merchant of Record for these transactions. This means Polar sells the Formstep subscription or credit pack to you, collects payment, issues the receipt or invoice, and handles applicable taxes on our behalf. When you subscribe or top up credits, you also agree to Polar’s terms of service and privacy policy in addition to these Terms. Your card details, billing address, and other payment information are collected and stored by Polar, not by Formstep. Formstep receives only the limited transaction data needed to provision your plan and credits and to handle support requests. Because Polar is the Merchant of Record, Polar determines and charges any value-added tax, goods and services tax, sales tax, or equivalent levy that applies to your purchase based on your billing location. The price displayed at checkout will reflect taxes where required. You are responsible for providing accurate billing and tax information, including any VAT number or business identifier if you are purchasing as a business. Receipts and, where applicable, tax-compliant invoices are issued by Polar and made available through the billing portal linked from your Formstep account. If you need a corrected invoice, contact Polar through the billing portal. If payment cannot be collected, your account may enter a grace period during which Pro features are progressively restricted, and may ultimately be downgraded to the free tier until the outstanding balance is paid, as described in the [billing documentation](https://formstep.io/docs/subscription-billing/upgrading-downgrading/#grace-period). You remain responsible for any amounts owed. ## 12. AI Credits Several Formstep features, such as AI-assisted form building and AI chat for analysing submissions, consume AI credits. Credits represent prepaid usage of the underlying AI models that power these features and are not a currency, store of value, or property right. No plan includes AI credits. You can purchase credit top-ups from Polar at any time, on any plan. **Purchased credits do not expire** and remain available on your account until they are used. Different AI operations consume different amounts of credits depending on the model used and the size of the input and output. The current consumption rates are published in the [Formstep documentation](https://formstep.io/docs/ai/ai-credits-usage/) and may be adjusted as model pricing or capabilities change. Credits are tied to your user account and cannot be transferred, gifted, resold, or exchanged for cash. Purchased top-up credits are not refundable once they are used, except where required by mandatory consumer protection law. Formstep applies fair use protections to AI Features to keep the Service stable and affordable for everyone. We may rate-limit, throttle, or temporarily pause AI operations if we detect automated abuse, attempts to extract model weights or training data, or usage patterns that materially exceed normal interactive use. Fair use thresholds, where defined, are published in the [Formstep documentation](https://formstep.io/docs/ai/ai-credits-usage/). If we suspect fraud or abuse, we may also suspend AI access while we investigate. ## 13. Cancellation, Refunds and Right of Withdrawal You can cancel your Pro subscription at any time from your Formstep billing settings. When you cancel, your subscription will not renew at the end of the current billing period. You keep access to Pro features until that period ends, after which your account is moved to the free tier. Purchased AI credits stay on your account. Your forms, responses, and workspace data remain available subject to the free tier’s limits. Formstep does not offer refunds for unused subscription time, unused AI credits, or partially used billing periods, except where required by mandatory consumer protection law or as provided by the [Data Processing Agreement](https://formstep.io/docs/legal/dpa/) in the event of termination following a subprocessor objection. This applies whether you cancel mid-cycle, downgrade, stop using the Service, or are downgraded automatically after a failed payment. Where Polar has issued an invoice, any refund decision is also subject to Polar’s policies as Merchant of Record. If you are a consumer resident in the European Union, the European Economic Area, or Norway, you have a statutory 14-day right to withdraw from a distance contract for digital services, without giving any reason. Because Polar, as Merchant of Record, collects your express prior consent to immediate delivery at checkout — in accordance with Article 16(m) of Directive 2011/83/EU and the Norwegian Right of Cancellation Act (Angrerettloven 2014, LOV-2014-06-20-27) — performance begins when the purchase is completed, and the right of withdrawal is exhausted from that point. ## 14. Respondent Payments via Stripe Connect Formstep lets you add Payment fields to your forms so that respondents can pay you when they submit a response, for example for bookings, donations, paid registrations, or product orders. These respondent payments are processed through Stripe Connect, and they are separate from your Formstep subscription and AI credit purchases. To accept respondent payments, you must connect your own Stripe account to Formstep. You are solely responsible for creating, verifying, and maintaining that Stripe account, for complying with Stripe’s Connected Account Agreement and other applicable Stripe terms, and for meeting any identification, tax, and regulatory requirements Stripe imposes on you. For respondent payments, you are the merchant of record. Formstep is not a party to the transaction between you and your respondent, does not sell your goods or services, does not handle the funds, and does not issue receipts or invoices on your behalf. Funds are processed by Stripe and settled to your connected Stripe account, subject to Stripe’s fees, holds, and payout schedule. As the merchant for these transactions, you are responsible for: - Describing accurately what the respondent is paying for, including price, currency, and any recurring terms. - Charging, collecting, reporting, and remitting any applicable taxes on the respondent’s payment. - Honoring the goods, services, or commitments the respondent has paid for. - Handling refunds, chargebacks, disputes, customer support, and any consumer law obligations toward your respondents. - Complying with all laws that apply to your business, including anti-money-laundering, sanctions, and sector-specific regulations. For data protection purposes, Stripe acts as an independent controller for the payment data it processes. Formstep’s ability to offer respondent payments depends on its ongoing compliance with Stripe’s platform agreement; if that relationship changes, the availability of Payment fields may be affected without notice. We are not liable for Stripe’s availability, fees, fund holds, account suspensions, or disputes between you and a respondent. You agree to indemnify Formstep against claims, losses, and expenses arising from your use of respondent payments, except to the extent caused by our own breach of these Terms. Formstep does not currently charge a platform fee on respondent payments. ## 15. Service Availability and Modifications Formstep does not offer a formal service level agreement (SLA). Uptime is best-effort; we cannot guarantee that the Service will be uninterrupted, error-free, or available at any specific time. From time to time we will perform maintenance, which may occur without advance notice. Formstep reserves the right to modify, suspend, or discontinue any part of the Service — including individual features, integrations, plans, and limits — at any time, with or without notice, and with or without liability to you, except as required by applicable law. Minor changes, performance improvements, and bug fixes may be deployed at any time. Some features are clearly labelled as beta, preview, or experimental. These are offered “as is” and may change, break, or be withdrawn without notice. You should not rely on beta features for production workflows or business-critical use, and any data, configurations, or behaviour tied to a beta feature may be reset when the feature graduates or is removed. ## 16. AI Features and Outputs Formstep includes AI-powered capabilities, including AI-assisted form building and AI chat for analysing submissions. These features are powered by Claude models made by Anthropic, which we run on Amazon Bedrock inside the EU, and the providers we use may change over time as we improve quality, latency, and cost. Any change of AI provider constitutes a new subprocessor and will be notified in accordance with [Section 19](#subprocessors) and the [Data Processing Agreement](https://formstep.io/docs/legal/dpa/). AI outputs are generated probabilistically and can be inaccurate, incomplete, biased, outdated, or misleading. You are responsible for reviewing every AI-generated output before relying on it, publishing it, sending it to respondents, or using it to make decisions. Do not treat AI suggestions as legal, medical, financial, or other professional advice. You remain solely responsible for the forms you publish and any decisions you make based on AI output. When you use AI Features, the prompts you submit and the relevant context from your account (such as form structure or submission samples you explicitly share with the AI) are sent to Amazon Bedrock for processing. We configure it so that, under the [AWS Service Terms](https://aws.amazon.com/service-terms/) governing Amazon Bedrock, your inputs and outputs are: - **Not used to train** any AWS or third-party foundation models; - **Not stored by AWS** after the request is processed, and handled in accordance with the AWS Service Terms and Amazon Bedrock data-protection commitments; and - Processed in line with the security and data protection commitments described in our [Privacy Policy](https://formstep.io/docs/legal/privacy-policy/) and [Data Processing Agreement](https://formstep.io/docs/legal/dpa/). Where AI processing transfers your data outside the European Economic Area, Formstep relies on the safeguards set out in [Section 10 of the Data Processing Agreement](https://formstep.io/docs/legal/dpa/#international-transfers), including Standard Contractual Clauses adopted under Decision (EU) 2021/914 and, where applicable, the EU–US Data Privacy Framework. You should not submit personal data, secrets, or other sensitive information through AI Features unless you have a lawful basis to share that information with AWS and have considered the risks. We do not control how AWS responds to lawful government requests directed at it. The prohibited uses in [Section 7](#prohibited-uses) apply to AI Features. You must not attempt to reverse-engineer the underlying models, extract their weights, or use the AI Features to build a competing product. ## 17. Third-Party Integrations Formstep connects to a range of third-party services so you can route submissions, sync data, and automate workflows. These integrations include messaging tools, webhook endpoints, automation platforms, spreadsheets, productivity suites, databases, and email providers. This Section 17 covers only third-party integrations that you optionally enable; the subprocessors Formstep relies on to deliver the Service are described in [Section 19](#subprocessors) and the [Data Processing Agreement](https://formstep.io/docs/legal/dpa/), and you cannot opt out of them while using the Service. Third-party services are operated independently and are not part of Formstep. We do not control their availability, pricing, features, terms, privacy practices, or uptime, and we are not responsible for outages, changes, deprecations, or data loss on their side. If a third-party service changes its API or terms in a way that breaks an integration, we cannot guarantee continued functionality. When you connect a third-party service, you authorise Formstep to exchange data with that service on your behalf. You are responsible for reviewing and complying with the terms and acceptable use policies of every service you connect, and for ensuring you have the right to share the data you route through Formstep. OAuth permissions you grant remain active until you revoke them, either inside Formstep or in the third-party service. If you use API keys or our MCP server to integrate Formstep with your own systems or with AI agents, you are responsible for keeping credentials secret, rotating them when needed, and for all activity performed using those credentials. We may rate-limit, suspend, or revoke API keys and MCP access that show signs of abuse, security risk, or that violate these Terms. ## 18. Data Export and Portability Your data belongs to you. While your account is active, you can export your forms and submissions at any time in standard formats, including CSV and Excel (XLSX), directly from the Formstep application. There is no limit on how often you can export your own data. Cancelling a Pro subscription does not delete your data; you keep access on the free tier subject to the free tier’s limits. If you **close your account**, the account and its forms and submissions are removed from active systems and remain in our providers’ backups only until the corresponding backup window expires, as described in the [Privacy Policy](https://formstep.io/docs/legal/privacy-policy/). We strongly recommend exporting any data you need before closing your account. If you are a Workspace owner and you close your account, the Workspace and all its associated forms, submissions, and data will be scheduled for deletion. Members’ own accounts are not affected by a Workspace owner closing their account. ## 19. Privacy and Data Processing How we collect, use, store, and share personal data is described in our [Privacy Policy](https://formstep.io/docs/legal/privacy-policy/), which forms part of these Terms by reference. For data submitted to your forms by respondents, you are the data controller and Formstep acts as your data processor. This means you decide what personal data to collect, why, and on what legal basis, and we process that data on your behalf and on your documented instructions. The terms of that processing relationship, including subprocessors, security measures, and international transfers, are set out in our [Data Processing Agreement](https://formstep.io/docs/legal/dpa/), which applies automatically to customers subject to the GDPR or equivalent regimes. For data we collect about you as a Formstep user, such as your account details, billing information, and usage of the product, Formstep is the data controller. We process this data to provide and improve the Service as described in the Privacy Policy. Formstep handles personal data in accordance with the EU General Data Protection Regulation (GDPR) and applicable Norwegian data protection law. If you have questions about how your data or your respondents’ data is handled, or if you need to exercise data subject rights, contact us at . In the event of a personal data breach affecting your account, Formstep will notify you in accordance with [Section 11 of the Data Processing Agreement](https://formstep.io/docs/legal/dpa/#breach), which sets out our notification obligations and timelines. ### Subprocessors Formstep relies on a small set of trusted third-party providers (“subprocessors”) to deliver the Service. These subprocessors process personal data on our behalf only as needed to provide their part of the Service, under contracts that require appropriate confidentiality and security measures. The current core subprocessors include: - **Convex** — application database and backend. - **Cloudflare R2** — object storage for file uploads and signatures. - **Cloudflare** — CDN, DNS, edge security, custom hostnames, and Turnstile bot protection. - **Polar** — subscription billing and AI credit purchases (Merchant of Record; independent controller for buyer billing data). - **Stripe** — respondent payment processing via Stripe Connect (independent controller for payment data). - **Amazon Web Services (AWS)** — Amazon Bedrock for AI Features; Amazon SES for transactional and system emails, including abandoned-response reminders on Pro workspaces. - **Axiom** — operational logs and monitoring. - **PostHog** — analytics and error reports for our website and the Formstep app, and account sign-ups. - **Unsplash** — image search API; selected images are stored in Cloudflare R2 and served from there. The authoritative list of subprocessors — with data categories and regions — is in our [Data Processing Agreement](https://formstep.io/docs/legal/dpa/). Changes to subprocessors are handled in accordance with [Section 9 of the Data Processing Agreement](https://formstep.io/docs/legal/dpa/#subprocessors). ## 20. Intellectual Property Formstep, including its software, source code, user interface, design system, illustrations, documentation, brand name, logos, and other trademarks, is owned by Formstep and protected under Norwegian and international intellectual property laws. All rights not expressly granted to you in these Terms are reserved. Subject to your compliance with these Terms, Formstep grants you a limited, non-exclusive, non-transferable, non-sublicensable, and revocable licence to access and use the Service for your internal business or personal purposes during the term of your account. This licence does not permit you to copy, modify, reverse engineer, decompile, resell, sublicence, or create derivative works based on the Service, except to the extent such restrictions are prohibited by mandatory law. You must not remove, obscure, or alter any proprietary notices on the Service, and you must not use Formstep’s name, logos, or other brand assets except as expressly permitted in writing or as needed to refer to the Service in accordance with applicable law. Nothing in this Section 20 affects the rights you retain in Your Content, as described in [Section 8](#user-content). ## 21. Feedback If you choose to send Formstep ideas, suggestions, feature requests, bug reports, or any other feedback regarding the Service (“Feedback”), you agree that such Feedback is non-confidential and that Formstep may use, reproduce, modify, distribute, and commercialise the Feedback for any purpose, without restriction, attribution, or obligation to compensate you. You waive any rights in the Feedback that would prevent Formstep from exercising the foregoing, to the extent permitted by applicable law, including moral rights under the Norwegian Copyright Act (Åndsverkloven) and equivalent laws in other jurisdictions. You are not required to provide Feedback, and submitting Feedback does not grant Formstep any rights in Your Content beyond those described in [Section 8](#user-content). ## 22. Termination by You You may cancel your subscription or close your account at any time through your account settings or by contacting . See [Section 13](#cancellation) for cancellation and refund terms and [Section 18](#export) for data handling after closure. ## 23. Termination and Suspension by Formstep Formstep may suspend or terminate your access to the Service, in whole or in part, at our sole discretion, with or without cause, and with or without notice, except where mandatory consumer protection law requires otherwise. For consumers on a paid subscription, termination without cause will not take effect before the end of the current paid billing period, and any prepaid fees for the unused remainder of that period will be refunded. Reasons may include: - You materially breach these Terms or any policy incorporated by reference; - You fail to pay fees when due and the failure is not remedied after reasonable notice; - Your use of the Service creates a security, legal, or operational risk to Formstep, its users, or third parties; - Formstep is required to do so by law, regulation, or a binding order from a competent authority; or - Formstep ceases to offer the Service or a specific feature. Where the circumstances permit, Formstep will give you advance notice of suspension or termination and a reasonable opportunity to cure the issue. In cases involving abuse, illegal activity, security incidents, or risk of harm, Formstep may act immediately and without prior notice. If Formstep suspends or terminates your account because you breached these Terms, for example through a prohibited use under [Section 7](#prohibited-uses), Formstep may end your paid subscription immediately. Fees already paid and purchased AI credits, used or unused, are not refunded, except where mandatory consumer protection law requires otherwise. Following termination, Formstep will handle your data in accordance with the Privacy Policy. Provisions that by their nature survive termination remain in effect, including [Section 8](#user-content) (Your Content and ownership), [Section 20](#intellectual-property) (intellectual property), [Section 21](#feedback) (feedback), [Section 24](#warranty-disclaimer) (warranty disclaimer), [Section 25](#limitation-of-liability) (limitation of liability), [Section 26](#indemnification) (indemnification), and [Section 27](#governing-law) (governing law and dispute resolution). ## 24. Warranty Disclaimer To the maximum extent permitted by applicable law, the Service is provided on an “as-is” and “as-available” basis, without warranties of any kind, whether express, implied, or statutory. Formstep specifically disclaims all implied warranties of merchantability, fitness for a particular purpose, non-infringement, accuracy, reliability, and any warranty arising out of course of dealing or usage of trade. Formstep does not warrant that the Service will be uninterrupted, error-free, secure against all threats, free of viruses or harmful components, or that defects will be corrected. You acknowledge that the Service depends on third-party infrastructure, networks, and services outside Formstep’s full control. Nothing in this section limits warranties or rights that cannot be excluded under mandatory Norwegian or EU consumer protection law. ## 25. Limitation of Liability To the maximum extent permitted by applicable law, in no event will Formstep be liable for any indirect, incidental, special, consequential, exemplary, or punitive damages, including loss of profits, revenue, goodwill, data, business opportunities, or anticipated savings, even if Formstep has been advised of the possibility of such damages. Formstep’s total aggregate liability arising out of or relating to these Terms or your use of the Service, whether in contract, tort (including negligence), statute, or otherwise, will not exceed (a) for users on a paid plan, the total fees paid for your Formstep Subscription and AI credit purchases through Polar as Merchant of Record during the twelve (12) months immediately preceding the event giving rise to the liability; or (b) for users on a free plan, one hundred euros (EUR 100). Any platform fee that Formstep may charge on respondent payments is treated as fees paid to Formstep for the purposes of calculating the cap in clause (a). The cap in clause (b) does not apply to liability that cannot be limited under mandatory Norwegian or EU consumer protection law, including liability towards a consumer for damage caused by Formstep’s gross negligence, wilful misconduct, or a personal data breach attributable to Formstep, or liability to any data subject under Article 82 GDPR. Formstep is not liable for any failure or delay in performance caused by events beyond its reasonable control, including acts of God, natural disasters, war, terrorism, civil unrest, labour disputes, governmental action, internet or telecommunications failures, power outages, or failures of third-party infrastructure or services (“Force Majeure”). Nothing in this section limits or excludes liability that cannot be limited or excluded under mandatory law, including liability for death or personal injury caused by negligence, fraud, fraudulent misrepresentation, gross negligence, wilful misconduct, or a personal data breach attributable to Formstep, or liability owed to you as a consumer under mandatory Norwegian or EU consumer protection law. ## 26. Indemnification To the extent permitted by applicable law, you agree to defend, indemnify, and hold harmless Formstep and its officers, employees, contractors, and agents from and against any third-party claims, demands, actions, losses, damages, liabilities, costs, and expenses (including reasonable legal fees) arising out of or relating to: - Your Content, including any claim that Your Content infringes third-party rights, violates law, or causes harm to a respondent or other person; - Your use of the Service in breach of these Terms or applicable law; - Your violation of any rights of a third party, including privacy, intellectual property, or contractual rights; or - Your responsibilities as a data controller for submissions you collect through the Service. This indemnity does not apply to the extent the claim arises from Formstep’s own negligence, wilful misconduct, or breach of these Terms. If you are a consumer, this section applies only to the extent permitted by mandatory consumer protection law, and nothing in this section limits your statutory rights. Formstep will notify you of any claim for which it seeks indemnification. ## 27. Governing Law and Dispute Resolution These Terms and any dispute or claim arising out of or in connection with them, their subject matter, or formation (including non-contractual disputes or claims) are governed by and construed in accordance with the laws of Norway, without regard to its conflict of laws principles. Subject to the mandatory consumer-protection carve-outs in this Section 27, the parties agree that the Oslo District Court (Oslo tingrett) has jurisdiction to settle any dispute arising out of or in connection with these Terms or the Service. If you are a consumer resident in the European Economic Area, the choice of law and forum above does not deprive you of the protection of mandatory consumer protection laws of the country in which you are habitually resident. You retain the right to bring proceedings against Formstep, and may be sued by Formstep, only in the courts of the country in which you are habitually resident. If you are a consumer resident outside the European Economic Area, mandatory consumer protections of the country in which you are habitually resident may also apply, in which case the choice of law and forum above does not override those protections to the extent they cannot lawfully be waived. Before initiating formal proceedings, you agree to first attempt to resolve any dispute informally by contacting Formstep at . Formstep is not obliged to participate in alternative dispute resolution before a consumer dispute resolution body, but will consider such requests in good faith. If you are a consumer resident in Norway, you may bring a complaint to the **Norwegian Consumer Authority** (Forbrukertilsynet, [forbrukertilsynet.no](https://www.forbrukertilsynet.no)) or the **Norwegian Consumer Council** (Forbrukerrådet, [forbrukerradet.no](https://www.forbrukerradet.no)). If you are a consumer resident in the European Union, you may also contact the European Commission’s consumer redress portal (accessible at [consumer-redress.ec.europa.eu](https://consumer-redress.ec.europa.eu)) for guidance on dispute resolution options in your country. ## 28. General Provisions ### Changes to These Terms Formstep may update these Terms from time to time to reflect changes in the Service, legal or regulatory requirements, or business practices. For material changes, Formstep will give you reasonable advance notice before the changes take effect. Your continued use of the Service after the effective date constitutes your acceptance of the updated Terms. If you do not agree to the changes, you may terminate your account before they take effect, as described in [Section 22](#termination-by-you). ### Entire Agreement These Terms, together with the Privacy Policy and any other policies or supplemental terms expressly incorporated by reference, constitute the entire agreement between you and Formstep regarding the Service, and supersede any prior or contemporaneous agreements, communications, or proposals on the same subject matter. ### Severability If any provision of these Terms is held to be invalid, illegal, or unenforceable by a court of competent jurisdiction, that provision will be enforced to the maximum extent permissible and the remaining provisions will continue in full force and effect. ### No Waiver Formstep’s failure to enforce any right or provision of these Terms does not constitute a waiver of that right or provision. Any waiver must be in writing and signed by an authorised representative of Formstep to be effective. ### Assignment You may not assign, transfer, or delegate these Terms or any of your rights or obligations under them, in whole or in part, without Formstep’s prior written consent. Any attempted assignment in breach of this section is void. Formstep may freely assign or transfer these Terms, in whole or in part, including in connection with a merger, acquisition, reorganisation, sale of assets, or by operation of law, provided that the assignee is bound by these Terms and your rights under them are not materially diminished. If you are a consumer, Formstep will give you reasonable advance notice before such an assignment takes effect, and you may terminate your account if you do not accept the assignment. ### Notices Formstep will provide notices to you by email to the address associated with your account, through the Service interface, or by other reasonable means. You are responsible for keeping your contact information accurate and up to date. You must send legal notices to Formstep by email to , and such notices are deemed received on the earlier of acknowledgement by Formstep or three (3) business days after sending. ### Language These Terms are written in English, which governs in any conflict with a translation, except where mandatory consumer protection law requires otherwise. ## Related documents [Privacy Policy](https://formstep.io/docs/legal/privacy-policy/) — How we collect, use, and share personal data. [Data Processing Agreement](https://formstep.io/docs/legal/dpa/) — Terms for processing personal data on your behalf. [Legal overview](https://formstep.io/docs/legal/overview/) — Index of Formstep legal documents. # Privacy Policy How Formstep collects, uses, and protects personal data, for the people who build forms and the respondents who fill them in, and the rights you have. > **Last updated: 13 November 2026** > > This Privacy Policy explains how Formstep handles personal data for everyone who visits our website, uses the product, or submits a response to a form built on Formstep. If you have any questions, contact us at . ## 1. Introduction Formstep is a form builder. People use it to create surveys, applications, registrations, checkouts, and other interactive documents, and to collect responses from their audiences. Personal data flows through the Service in two distinct ways: data that Formstep collects about its own users (the people who build forms), and data that respondents submit through forms built by those users. This Privacy Policy describes both. We have tried to keep it short, plain, and honest. Where a section applies only to one group, we say so. Capitalized terms not defined here have the meanings set out in our [Terms of Service](https://formstep.io/docs/legal/terms-of-service/). For data protection roles, references to “you” in this policy correspond to the “Customer” (and, under the GDPR, the “Controller”) in our [Data Processing Agreement](https://formstep.io/docs/legal/dpa/). By using Formstep, you acknowledge that we process personal data as described in this policy. If you do not agree, do not use the Service. ## 2. Who We Are The Service is operated by **Formbase AS**, a Norwegian limited company with organisation number 937 921 217 and registered office in Oslo, Norway (“Formstep”, “we”, “us”, “our”). For the personal data we collect about users of the Service — such as your account, billing, and product usage data — Formstep is the data controller. For the personal data that respondents submit through your forms, **you** (the Formstep user who built the form) are the data controller, and Formstep acts as your data processor. The terms of that processing relationship are described in the [Data Processing Agreement](https://formstep.io/docs/legal/dpa/), which applies automatically to customers subject to the GDPR or equivalent regimes. This Privacy Policy still tells respondents what Formstep technically does with their data on your behalf, but it does not replace your own privacy notice. For questions, complaints, or data subject requests, contact us at . ## 3. Scope This Privacy Policy covers personal data that Formstep collects, uses, or shares when: - You visit **formstep.io** or any of its subdomains; - You sign up for, sign in to, or use **the Formstep application** at app.formstep.io; - You **submit a response** to a form that is hosted on Formstep or embedded on another website; - You **contact us** for support, sales, partnerships, or anything else; - You read or interact with our **marketing emails, social channels, or events**. It does not cover third-party websites, services, or integrations that we link to or that you choose to connect, even if they are reached from inside Formstep. Those services have their own privacy policies, and you should review them. ## 4. Personal Data We Collect We try to collect only what we need. The categories below describe the personal data we may handle in different parts of the Service. ### Account data When you create a Formstep account, we collect: - Your **email address**, used to sign in and receive system messages. - A **display name and avatar** if you set them, used to identify you to your collaborators. - Authentication data linked to **sign-in methods** you choose, such as magic-link login, Google sign-in, or anonymous guest sign-in for short-lived guest sessions. - **Workspace settings** you set up, including workspace name, timezone, and the list of members you have invited. Your email address is a contractual requirement for using the Service — it is the only data required to register, and without it we cannot create your account. Display name, avatar, and workspace settings are optional. ### Subscription and billing data Formstep subscriptions and AI credit purchases are sold through **Polar** (polar.sh), our payment provider and Merchant of Record. Polar collects and stores your payment details directly. Formstep receives only the limited information needed to provision your plan and credits, including: - A Polar customer identifier linking your Formstep account to your Polar customer record; - A Polar subscription identifier and the chosen billing interval (monthly or annual); - Subscription lifecycle timestamps such as the current period end, lapse, and grace-period markers used to apply your plan entitlements; - Order and credit-top-up identifiers used to provision AI credits. Formstep does not see or store payment card details, tax identifiers, or billing addresses — Polar holds those records directly, and you can access them through the Polar billing portal. Where you accept respondent payments through the Payment field, Stripe collects all card data directly; Formstep receives only non-sensitive references such as the last four digits. ### Form content you create The forms you build on Formstep — including questions, copy, media, conditional logic, and styling — are stored on our infrastructure so we can display them to respondents and let you edit them later. Form configuration is treated as Your Content under our [Terms of Service](https://formstep.io/docs/legal/terms-of-service/). ### Submission data from respondents When someone submits a response to one of your forms, we store on your behalf: - The **answers and uploads** the respondent provides, exactly as they entered them; - A **submission timestamp**, the respondent’s browser language, the workspace timezone (captured at submit), and identifiers that link the submission to your form; - **Drafts and partial submissions** saved server-side while the respondent fills the form, plus timestamps used to power abandoned-response reminders (when you enable them); - **Technical analytics signals** captured for every published form, including approximate country (derived from the respondent’s timezone or the Cloudflare edge `CF-IPCountry` header), device type, browser, traffic source, and engagement duration; - A **respondent-side visitor identifier** used to deduplicate form view and engagement counts — see [Cookies and Similar Storage](#cookies) for details; - Where you use the Payment field, a reference to the **Stripe payment** the respondent completed (the card data itself is handled by Stripe, not by Formstep); - For a **request** you send to one named recipient, their name and email address and the prefilled values, hidden context, metadata and documents you send with it, plus the history of its invitation, reminder and callback deliveries. Respondent IP addresses are processed transiently for rate limiting, Cloudflare Turnstile bot protection, and abuse prevention, and are not persisted alongside submissions. You decide what fields to include and which of these technical signals to capture. You are the data controller for submissions — see [Section 2](#who-we-are) for data protection roles. Form submissions may include special categories of personal data under Article 9 GDPR — such as health information, political opinions, or biometric data — depending on the questions you ask. As the controller, you are responsible for establishing a valid legal basis under Article 9(2) GDPR before collecting such data and for making any required disclosures to respondents. ### Product usage and device data When you use the Formstep application, we automatically collect: - **Activity logs** such as which pages you load, what features you use, and when, used to operate the product, debug issues, and understand which features matter. - **Device and browser data** such as user-agent, screen size, operating system, language, and approximate location derived from IP. - **IP address**, used to deliver the Service, detect abuse, rate-limit, and meet security obligations. - **Diagnostic data** such as error reports and performance traces when the application encounters a problem, used to diagnose and fix the issue. ### Cookies and similar technologies We use cookies and similar storage mechanisms to keep you signed in, remember your workspace and preferences, and secure the application. On our website and in the Formstep app, where the law asks us to ask first, analytics cookies are set and your visits recorded only if you accept in the cookie banner on our website; elsewhere they are set until you decline. The partner referral cookies on our website follow the same choice. See [Cookies and Similar Storage](#cookies) for details and the choices you have. ### AI feature data When you use AI Features — such as AI-assisted form building or AI chat for analysing submissions — we process: - The **prompts** you send to the AI; - The **context** the feature passes alongside the prompt, such as the structure of the form you are editing or submission data you explicitly share; - The **outputs** the AI returns; - **Credit consumption metadata**, so we can show your remaining balance and deduct usage correctly. These inputs and outputs are sent to Amazon Bedrock, which runs Claude models made by Anthropic, to generate the response. We may add or change providers over time and will update this Privacy Policy and the subprocessor list in the [Data Processing Agreement](https://formstep.io/docs/legal/dpa/) accordingly. See [AI Features](#ai) below for the contractual protections we have in place. ### Support and communications data When you email us, fill out a contact form, or chat with us, we keep the contents of the conversation, the contact details you provide, and any context needed to assist you. Recordings and transcripts of demo calls, if applicable, are stored only with your prior consent. ### Where this data comes from Most personal data comes directly from you. Some is received from Google (sign-in profile), Polar (billing metadata), Stripe (transaction references, not card data), or derived from your IP address (approximate country for analytics). ## 5. How We Use Personal Data We use personal data for the following purposes, and only when one of the legal bases in [Section 6](#legal-bases) applies. | Purpose | What this means in practice | | - | - | | Provide the Service | Run Formstep, show your forms to respondents, store submissions, send notifications, run integrations you enable, and apply your settings. | | Manage your account | Create your account, authenticate you, route you to the right workspace, manage roles and permissions, and apply plan entitlements. | | Process payments | Provision Pro features, top up AI credits, reconcile invoices issued by Polar, and handle billing support requests. | | Operate AI Features | Send the prompts and context you submit to our LLM subprocessors, return their outputs to you, and meter your AI credit usage. | | Secure the Service | Detect and prevent abuse, fraud, spam, account takeover, and other attacks, and investigate incidents when they occur. | | Support you | Respond to your questions, debug issues you report, and keep a record of the conversation so we can follow up. | | Improve the product | Understand which features are used, how the product performs, and where it breaks, so we can make it better. We rely on aggregated and de-identified data wherever possible. | | Communicate with you | Send system emails (account, billing, security) and service announcements. | | Comply with law | Meet our legal obligations, respond to lawful requests from authorities, exercise or defend legal claims, and enforce our Terms. | We do **not** sell personal data, and we do not share personal data with advertisers or data brokers for advertising purposes. For AI data-handling and no-training commitments, see [Section 13](#ai). ## 6. Legal Bases for Processing If you are in the European Economic Area, the United Kingdom, Switzerland, or another jurisdiction with a similar regime, we process personal data only when one of the following legal bases under the GDPR applies: - **Contract** (Article 6(1)(b)) — providing the Service, managing your account, processing payments, operating AI Features, and providing support. - **Legitimate interests** (Article 6(1)(f)) — where our interest does not override your rights. Specifically: - _Securing the Service_ — our interest in keeping Formstep and its users safe from abuse, fraud, and attacks. - _Improving the product_ — our interest in understanding how Formstep is used so we can make it better, relying on aggregated and de-identified data wherever possible, and in finding and fixing faults. This covers the product steps and error reports described in [Cookies and Similar Storage](#cookies). - _Service communications_ — our interest in telling you about changes to features you already use. - _Counting visits_ — our interest in knowing how many people visit our website and the Formstep app and which pages they view, measured without cookies as described in [Cookies and Similar Storage](#cookies). - _Understanding how our website and app are used_ — outside the regions where we ask first, our interest in seeing how visitors use our website and how people use the Formstep app, through analytics cookies and session recordings, as described in [Cookies and Similar Storage](#cookies). You can object at any time through Cookie settings in the website footer. - _Crediting partners who refer customers_ — outside the regions where we ask first, our interest in paying the partners in our affiliate program for the customers they refer, through the referral cookie described in [Cookies and Similar Storage](#cookies). You can object at any time through Cookie settings in the website footer. - **Consent** (Article 6(1)(a)) — analytics cookies and session recordings on our website and in the Formstep app, and the partner referral cookie on our website, for visitors in the regions where we ask first, and optional product data sharing. You can withdraw consent at any time, for example through Cookie settings in the website footer. - **Legal obligation** (Article 6(1)(c)) — tax, accounting, sanctions, and other legal obligations. - **Vital interests** (Article 6(1)(d)) — only in rare cases to protect someone’s life or physical safety. Where we rely on legitimate interests, you have the right to object, and we will reconsider on the basis of your specific situation. ## 7. How We Share Personal Data We share personal data only when one of the following applies. ### With subprocessors that help us run Formstep We rely on a small set of trusted third-party providers to deliver the Service. They process personal data only as needed to provide their part of the Service, under contracts that require appropriate confidentiality and security measures. The core subprocessors include: - **Convex** — application database and backend. - **Cloudflare R2** — object storage for file uploads and signatures. - **Cloudflare** — CDN, DNS, edge security, custom hostnames, and Turnstile bot protection. - **Polar** — subscription billing and AI credit purchases (Merchant of Record; independent controller for buyer billing data). - **Stripe** — respondent payment processing via Stripe Connect (independent controller for payment data). - **Amazon Web Services (AWS)** — Amazon Bedrock for AI Features; Amazon SES for transactional and system emails, including abandoned-response reminders on Pro workspaces. - **Axiom** — operational logs and monitoring. - **PostHog** — analytics and error reports for our website and the Formstep app, and account sign-ups. - **Affonso** — our affiliate program: records which partner referred a visitor and credits that partner when the visitor subscribes. - **Unsplash** — image search API; selected images are stored in Cloudflare R2 and served from there. The authoritative, up-to-date list of subprocessors, along with the categories of data each one processes and the regions in which they operate, is maintained in our [Data Processing Agreement](https://formstep.io/docs/legal/dpa/). ### With integrations you connect When you connect a third-party service to Formstep — such as a webhook endpoint, automation platform, spreadsheet, messaging tool, or CRM — you authorise us to send the relevant data to that service on your behalf. We share only what is needed for the integration to work, and the third party becomes responsible for the data once it receives it under its own terms and privacy policy. ### With AI assistants you connect You can connect an AI assistant, such as ChatGPT or Claude, to Formstep through our MCP server. You sign in, choose one workspace and approve the connection. When the assistant then calls a Formstep tool, we return what that tool reads to the assistant, on your instructions. Depending on what you ask, this can include form content, recipients’ names and email addresses, request details, submission answers, request activity, and analytics about form visitors such as device, browser, country and traffic source. The company that provides the assistant handles that data under its own terms and privacy policy, and Formstep does not control what it does with it. You can revoke a connection at any time from Connected apps & API Keys, which stops further access but does not delete what the assistant has already received. ### With your collaborators If you invite people to your workspace, they will see your name, email, role, and the forms and submissions they have permission to access. Workspace owners can change member access and request ownership reassignment through support; the available workspace roles are described in [Section 5 of the Terms of Service](https://formstep.io/docs/legal/terms-of-service/#workspaces). ### For legal and safety reasons We may disclose personal data when we believe in good faith that disclosure is necessary to (a) comply with applicable law or a binding order from a competent authority, (b) enforce our [Terms of Service](https://formstep.io/docs/legal/terms-of-service/), (c) detect, prevent, or address fraud, security, or technical issues, or (d) protect the rights, property, or safety of Formstep, our users, respondents, or the public. ### In a business transfer If Formstep is involved in a merger, acquisition, reorganisation, sale of assets, or insolvency, personal data may be transferred to the relevant counterparties as part of that transaction. Any such transfer will be subject to applicable data protection law. ## 8. International Data Transfers Formstep is based in Norway, and we aim to minimise transfers outside the European Economic Area. Some of our subprocessors are located in, or transfer data to, jurisdictions outside the EEA, including the United States and the United Kingdom. When personal data leaves the EEA, we rely on Standard Contractual Clauses (SCCs) adopted under Decision (EU) 2021/914, the UK International Data Transfer Addendum, the Swiss FDPIC addendum, adequacy decisions, or the EU–US Data Privacy Framework (DPF), as applicable. We also apply encryption in transit and at rest, restricted access, and contractual data minimisation. The applicable transfer mechanism for each subprocessor is detailed in our [Data Processing Agreement](https://formstep.io/docs/legal/dpa/#international-transfers). ## 9. Data Retention We keep personal data only as long as we need it for the purposes described in this policy and as required by law. - **Account data** is kept while your account is active. When you ask us to delete your account, account records are removed from active systems shortly after the request is processed and remain in backups only until the corresponding backup window expires. - **Form configuration and submissions** are kept while your workspace exists and you have not deleted them. Cancelling a Pro subscription moves you to the free tier; only **closing your account** triggers deletion. You can also configure a per-form submission retention period; submissions are automatically removed by a scheduled cleanup job when that period elapses. - **Requests** — the recipient’s name and email address and what you sent with the request — follow the same rules. A pending request ends at its expiry. One that expired or was canceled without an answer is deleted after the same period as draft submissions, 30 days by default, counted from when it ended. A completed one is kept until you delete it, delete its submission, or the form’s retention period passes, and then it is deleted with its submission. - **Draft submissions** that a respondent starts but does not submit are purged after 30 days, unless you have configured a different retention. - **Trashed forms** that you have moved to trash are permanently purged after 30 days. - **Anonymous (guest) sessions** created when you try Formstep without an account are purged automatically after 1 day. - **Form analytics** — page and question views are kept for up to 365 days. - **Delivery logs** — records of submission processing are kept for 30 days after they finish, and records of notifications for 90 days. Integration event logs are kept for 90 days. - **Connected assistants and apps** — an access or refresh token’s record is deleted 7 days after it expires. Revoking a connection stops access straight away. - **Billing records** are kept for the period required by applicable accounting and tax law. Polar, as Merchant of Record, retains its own billing records under its own retention policy. - **Support communications** sent to or from the Formstep support inbox are kept as long as needed to resolve your request and handle any related legal claims. - **Product usage and device data** — activity logs, device metadata, and diagnostics are retained in identifiable form for a limited period and then deleted or aggregated so that individual users can no longer be identified. - **Operational and security logs** generated by the Service are kept for the period needed to operate, debug, and secure the Service. Where a specific incident requires a longer period, we restrict access and delete the logs as soon as the obligation expires. - **Backups** are created on a regular schedule with a short retention window. File uploads stored in Cloudflare R2 are not covered by object versioning; deletions of file uploads are unrecoverable immediately. Personal data deleted from production becomes unrecoverable once the corresponding backup window expires. - **AI prompts and outputs** sent to Amazon Bedrock are not stored by AWS after the request is processed and are not used to train any models, as described in the [AWS Service Terms](https://aws.amazon.com/service-terms/). If law requires us to keep a record for longer — for example to meet a tax, anti-money-laundering, or court-order obligation — we restrict access to that record and delete it as soon as the obligation expires. ## 10. Security We apply technical and organisational measures designed to protect personal data against accidental or unlawful destruction, loss, alteration, unauthorised disclosure, and unauthorised access. These measures include: - Encryption of personal data **in transit** using TLS 1.2 or higher, and **at rest** using industry-standard algorithms; - **Access controls** based on least privilege, with internal access to production systems restricted on a need-to-know basis; - **Logging and monitoring** of administrative actions in production systems; - **Network segmentation** and isolation of production from development environments; - **Vulnerability management**, including timely application of security updates and a responsible disclosure channel at ; - **Access controls** limiting production access to authorised personnel. No system is perfectly secure. If you become aware of a vulnerability or a possible compromise, report it to . ## 11. Your Rights Depending on where you live, you may have the following rights over the personal data we hold about you. Many of these are guaranteed under the GDPR, the UK GDPR, and equivalent laws in other jurisdictions. - **Right of access** — ask for a copy of the personal data we hold about you and information about how it is processed. - **Right to rectification** — ask us to correct inaccurate or incomplete data. - **Right to erasure** — ask us to delete personal data when there is no overriding reason to keep it. You can also close your account and delete most of your data yourself from the product. - **Right to restriction** — ask us to pause processing in certain situations, for example while we check a correction or objection. - **Right to data portability** — receive your personal data in a structured, commonly used, machine-readable format, or have it sent to another controller. The export feature in the application covers most of this for forms and submissions, with CSV and Excel (XLSX) output. - **Right to object** — object to processing based on legitimate interests, including profiling, and to direct marketing at any time. - **Right to withdraw consent** — where we rely on your consent, you can withdraw it at any time. Withdrawing consent does not affect the lawfulness of processing before withdrawal. - **Right not to be subject to a decision based solely on automated processing** (Article 22 GDPR) — we do not make decisions about you that produce legal or similarly significant effects through solely automated processing, including profiling. To exercise any of these rights, email or use the self-service options in the application where available. We will respond within one month, or tell you why we need more time. We may ask for information to verify your identity before acting on a request, especially for access or deletion. If you are unhappy with how we have handled your data, you can lodge a complaint with a supervisory authority — for users in Norway, the **Norwegian Data Protection Authority** (Datatilsynet, [datatilsynet.no](https://datatilsynet.no)). You can also complain to the authority in the EEA country where you live or work. ### Rights of respondents If you are a respondent who submitted a form built on Formstep, your data subject rights run primarily against the Formstep user who built that form — they are the data controller. We can help you reach them, and we will support them in responding to your request. Where we hold the underlying data as a processor on their behalf, we will act on their documented instructions. To contact us, write to and tell us which form you submitted (the URL is usually enough). ## 12. Cookies and Similar Storage We use a small number of cookies, `localStorage`, and `sessionStorage` entries to make Formstep work and to deduplicate analytics on your forms. We try to keep the list short. - **Strictly necessary** — keep you signed in, remember the workspace you last used, secure the application against cross-site request forgery, and store essential preferences. These cannot be disabled without breaking the product. - **Functional** — remember UI preferences such as theme, sidebar state, and language. You can clear them from your browser. - **Analytics on our website (formstep.io) and in the Formstep app (app.formstep.io)** — PostHog, our analytics provider, can count a visit without storing anything on your device. Its servers combine your IP address and browser user agent with a random value that changes every day and is then deleted, so a visit can’t be linked to your visits on other days. If you visit from the European Economic Area, the United Kingdom and its Crown dependencies, Gibraltar, Switzerland, Turkey or Quebec, or from a location we can’t determine, we ask first: only if you accept in the cookie banner does PostHog also set a cookie (`ph_*_posthog`) and `localStorage` entries that identify your browser, and record your visits as session replays. Everywhere else, PostHog sets that cookie and records session replays from your first page, unless your browser sends a Global Privacy Control signal. We tell the regions apart by the country our hosting provider, Cloudflare, derives from your IP address. Session replays hide what you type into fields. Your choice is stored in a cookie (`fs_analytics_consent`) so we don’t ask on every page. You can change it at any time through **Cookie settings** in the website footer; declining or withdrawing deletes what PostHog stored, and your visits are then counted without cookies. The cookie that holds your choice, and PostHog’s cookie, are set for formstep.io and its subdomains, so the same choice applies in the Formstep app, which has no banner of its own: there PostHog sets its cookie and records your use only if you accepted on our website, or if you are outside the regions where we ask first and have not declined. Once you sign in, your use of the app is linked to your account identifier; we do not send PostHog your name or email address. Session replays of the app after you sign in show Formstep’s own interface, such as its buttons, menus and headings, with the layout of the screen and where you click. They hide everything else: text is masked unless it is one of Formstep’s own labels, the form you are editing, every table and the places that show your respondents’ answers or details are masked whole, and images are left out, so replays do not show the wording of your forms, your respondents’ answers or their contact details. A click is recorded with the label of the button you pressed when that label is Formstep’s own. Replays of the guest editor, where you try Formstep without an account, show the screen as you see it, including the form you build there and what you write to the AI assistant; they leave out images, what you type into fields, and the answers you give to the demo request. - **Product steps and error reports in the Formstep app** — these use no cookies and store nothing on your device, so they do not depend on the choice above. When you have an account, our servers tell PostHog when you take one of a few steps: your first workspace is set up, you create a form, edit a form for the first time, publish a form, or create a request. Each is recorded against your account identifier with the identifier of the record and how the step was taken (in the app, or through the API or MCP), and never with the content of a form or anything about your respondents. When the app fails, it sends PostHog an error report: the kind of error, where in our code it happened and, for a request our servers refused, the server’s error code. Text an error message quotes is removed before the report is sent. We use both to find where people get stuck and to fix faults; to object, write to . - **Partner referrals on our website (formstep.io)** — when you arrive through a link from a partner in our affiliate program, Affonso, our affiliate provider, records the visit for that partner and stores the referral in cookies (`affonso_referral`, and `affonso_data` with details about the partner) for 30 days. The cookies are set for formstep.io and its subdomains, so the Formstep app reads the referral when you subscribe: we then apply the partner’s offer, if there is one, and pay the partner a commission, and the partner learns that their referral became a customer. The cookies follow your analytics choice above: where we ask first, or when your browser sends a Global Privacy Control signal, they are set only if you accept, and until then Affonso records only that a partner’s link was used, without storing anything on your device: a click identifier (`affonso_id`) in the page address carries the visit from page to page instead. Declining or withdrawing deletes the cookies and removes the identifier from the address. - **Analytics on hosted form pages** — a random visitor identifier (`fs_visitor_id`) in `localStorage`, plus short-lived `sessionStorage` flags (`fs_viewed`, `fs_engaged`), used to deduplicate view and engagement counts. This storage is not needed for the form to function and does not enable advertising, cross-site tracking, or profiling. Formstep places this storage as a data processor on your instructions as controller. If your respondents are in jurisdictions where prior consent is required for non-essential storage (such as the EEA under the ePrivacy Directive or Norway under Ekomloven § 3-15), you are responsible for obtaining that consent before the form page loads. We do not use cookies or similar storage to track respondents across other websites for advertising, and we do not place advertising cookies on hosted form pages. You can clear cookies, `localStorage`, and `sessionStorage` through your browser settings. Blocking strictly necessary cookies or storage may prevent parts of the Service from working. If you build forms on Formstep, your hosted forms will set `fs_visitor_id` in `localStorage` on respondent devices as described above. You should disclose this in your own privacy notice to respondents. ## 13. AI Features Prompts and context you submit through Formstep AI Features are sent to Amazon Bedrock, which runs Claude models made by Anthropic inside the EU. Formstep’s own accounts, including forms Formstep itself publishes, and its test environments may instead use Anthropic’s API, which can process data outside the EU, keeps it for up to 30 days and does not use it to train models. Customer workspaces always run on Amazon Bedrock. Context includes, where applicable, the structure of the form you are editing and submission samples you explicitly share for analysis or summarisation. We configure it so that, under the AWS Service Terms, inputs and outputs are not used to train any models and are not stored by AWS after the request is processed. Processing is in line with the security and data protection commitments in this Privacy Policy and the [Data Processing Agreement](https://formstep.io/docs/legal/dpa/). AI outputs are generated probabilistically and may be inaccurate, incomplete, or out of date. You are responsible for reviewing every AI-generated output before you rely on it, publish it, or send it to respondents. You should not submit personal data, secrets, or other sensitive information through AI Features unless you have a lawful basis to share that information with our subprocessors and have considered the risks. We do not control how providers respond to lawful government requests directed at them. ## 14. Children Formstep is not designed for children. You must be at least sixteen (16) years old to create a Formstep account, as described in our [Terms of Service](https://formstep.io/docs/legal/terms-of-service/). Formstep users may build forms intended for younger audiences. If you collect personal data from children through Formstep, you are responsible for obtaining any required parental or guardian consent and for handling that data in accordance with applicable law, including COPPA where relevant. [Section 6 of the Terms of Service](https://formstep.io/docs/legal/terms-of-service/#acceptable-use) lists sensitive data classes the Service is not designed for, and [Section 7](https://formstep.io/docs/legal/terms-of-service/#prohibited-uses) sets out prohibited uses, including content that exploits or endangers minors. We do not knowingly collect personal data from children under sixteen (16) (or under thirteen (13) where COPPA sets a lower threshold). If you believe we have inadvertently collected personal data from a child below the applicable age threshold without the required consent, contact us at and we will delete it without undue delay. ## 15. Communications We send two kinds of email: - **System emails** about your account, security, billing, and the forms you build. These are part of the Service and cannot be opted out of while your account is active. - **Service announcements** about changes to the product, policies, or features you rely on. We keep these to the minimum needed to keep you informed. ## 16. Notice for California Residents If you are a resident of California, you have additional rights under the California Consumer Privacy Act (CCPA) as amended by the California Privacy Rights Act (CPRA), including the right to know what personal information we collect, the right to delete it, the right to correct inaccuracies, and the right to opt out of the “sale” or “sharing” of personal information. Formstep does not sell personal information and does not share personal information for cross-context behavioural advertising. Because no sale or sharing occurs, no “Do Not Sell or Share My Personal Information” link is required or provided. The categories of personal information we collect, purposes, and retention periods are described in [Section 4](#data-we-collect), [Section 5](#how-we-use), and [Section 9](#retention). We do not build behavioural profiles for advertising and do not use sensitive personal information for inferences or profiling. To exercise your rights — including the right to know, delete, correct, opt out, or limit sensitive personal information — write to . We will not discriminate against you for exercising any of these rights. ## 17. Changes to This Privacy Policy We may update this Privacy Policy from time to time to reflect changes to the Service, our subprocessors, applicable law, or business practices. The “Last updated” date at the top of this policy shows when it was most recently revised. If you continue to use the Service after a change takes effect, you accept the updated Privacy Policy. If you do not agree to a change, you can close your account before it takes effect. ## 18. Contact Us For privacy questions, data subject requests, or anything else related to this policy, contact us at: - **Privacy questions, data subject requests, and security reports** — Formstep has not appointed a Data Protection Officer. Our privacy team monitors the inbox and will route your request appropriately. ## Related documents [Terms of Service](https://formstep.io/docs/legal/terms-of-service/) — The rules that govern your use of Formstep. [Data Processing Agreement](https://formstep.io/docs/legal/dpa/) — Terms for processing personal data on your behalf. [Legal overview](https://formstep.io/docs/legal/overview/) — Index of Formstep legal documents. # Data Processing Agreement How Formstep processes personal data on your behalf under the GDPR and similar laws, with each party’s obligations, subprocessors, and security measures. > **Last updated: 13 November 2026** > > This Data Processing Agreement (“DPA”) forms part of the [Terms of Service](https://formstep.io/docs/legal/terms-of-service/) between you (the “Customer”) and Formstep (the “Processor”). It applies automatically whenever you use Formstep to collect, store, or otherwise process personal data of identifiable individuals subject to the General Data Protection Regulation (Regulation (EU) 2016/679, “GDPR”), the UK GDPR, the Norwegian Personal Data Act (personopplysningsloven), or another applicable data protection law that requires a written processing agreement. > > No signature is required. Your use of Formstep to process personal data constitutes acceptance of this DPA. ## 1. Parties This DPA is entered into between: - **Customer** — the natural or legal person who has accepted the Formstep [Terms of Service](https://formstep.io/docs/legal/terms-of-service/) and on whose behalf personal data is processed (acting as the “Controller”). - **Formstep** — Formbase AS, a Norwegian limited company with organisation number 937 921 217 and registered office in Oslo, Norway (acting as the “Processor”). Where the GDPR or another applicable law refers to the parties as “controller” and “processor”, or “data exporter” and “data importer”, those terms map to Customer and Formstep respectively for the purposes of this DPA. ## 2. Definitions Terms used in this DPA but not defined here have the meanings given in the [Terms of Service](https://formstep.io/docs/legal/terms-of-service/) or in the GDPR. In particular: - **Service** — the Formstep platform, including the form editor, published forms, submission management, integrations, AI Features, APIs, and all related functionality provided under the [Terms of Service](https://formstep.io/docs/legal/terms-of-service/). - **Form** — an online form, survey, quiz, or other data-collection page created and published by Customer through the Service. - **Submission** — a completed or partial response submitted by a Data Subject through a Form, including all field values, file uploads, metadata, and associated timestamps. - **Workspace** — the organisational unit within the Service under which Customer manages Forms, Submissions, members, and integrations. - **AI Features** — the AI-powered capabilities of the Service, including AI form generation, AI chat, and any other feature that sends data to a third-party AI model for inference. - **Personal Data** — any information relating to an identified or identifiable natural person, as defined in Article 4(1) GDPR, that Customer processes through the Service. - **Processing** — any operation performed on Personal Data, as defined in Article 4(2) GDPR. - **Data Subject** — the identifiable individual to whom Personal Data relates, including respondents who submit Forms, members of a Workspace, and Customer’s own end users. - **Subprocessor** — any third party engaged by Formstep to process Personal Data on its behalf in connection with the Service. - **Standard Contractual Clauses** or **SCCs** — the standard contractual clauses for the transfer of personal data to third countries adopted by the European Commission in Decision (EU) 2021/914, including the UK International Data Transfer Addendum where relevant. - **Applicable Data Protection Law** — the GDPR, the UK GDPR, the Norwegian Personal Data Act, and any other privacy or data protection law that applies to Customer’s processing of Personal Data through the Service. ## 3. Scope and Roles This DPA governs Formstep’s Processing of Personal Data on behalf of Customer through the Service. For that Processing: - Customer is the **Controller**. Customer determines the purposes and means of the Processing, decides what Personal Data to collect through Forms, configures retention and sharing settings, and connects integrations. - Formstep is the **Processor**. Formstep processes Personal Data on Customer’s documented instructions, primarily to operate the Service, deliver the features Customer has enabled, and meet its security and support obligations. For Personal Data that Formstep collects directly about Customer as a Formstep user — such as account details, subscription metadata received from Polar, and product usage telemetry — Formstep acts as an independent **Controller**. That Processing is governed by the [Privacy Policy](https://formstep.io/docs/legal/privacy-policy/), not by this DPA. Payment and buyer billing data is controlled by Polar, which acts as an independent controller as Merchant of Record. For respondent payments processed through Stripe Connect, Customer is the merchant of record and Controller of the payment-related Personal Data; Stripe acts as an independent controller for payment processing. See [Annex 1](#annex-1) for details. ## 4. Subject Matter, Nature, Purpose and Duration Formstep processes Personal Data only as needed to provide the Service to Customer under the Terms of Service. - **Subject matter** — the hosting and operation of online forms, the collection and storage of Submissions, and the provision of related features such as analytics, integrations, AI Features, notifications, and exports. - **Nature** — collection, storage, and all technical operations required to run the Service. - **Purpose** — to enable Customer to build and publish Forms, collect responses from Data Subjects, manage Submissions, and connect to third-party tools, all in accordance with Customer’s configuration and instructions. Customer’s instructions may be given through the web application, the API, MCP (Model Context Protocol) connections, or other programmatic interfaces made available as part of the Service. - **Duration** — for as long as Customer’s account is active and Personal Data is stored in the Service, plus any limited retention period set out in [Section 12](#return-deletion). ## 5. Categories of Data Subjects and Personal Data ### Categories of Data Subjects Personal Data processed under this DPA may relate to: - Respondents who submit Customer’s Forms; - Customer’s Workspace owners and members; - Customer’s contacts, leads, or end users whose data Customer imports or routes through integrations; - Any other Data Subjects whose data Customer chooses to process through the Service. ### Categories of Personal Data Customer decides what Personal Data to collect. The categories typically include: - **Identification data** — names, email addresses, phone numbers, postal addresses. - **Form responses** — free-text answers, choices, ratings, file uploads, signatures, payment transaction references, and any other content Data Subjects enter. - **Draft and partial responses** — submission drafts and partial responses saved server-side while a Data Subject is filling a Form, together with the timestamps used to power abandoned-response reminders where Customer has enabled that feature. - **Request data** — for a Request sent to one named recipient, the recipient’s name and email address, and the values Customer sends with it: prefilled answers, hidden-field context, metadata and documents for that recipient, together with the invitation, reminder and callback history. - **Profile and account data** — Workspace member emails, display names, role and permission settings. - **Technical data** — timestamps, approximate country, device type, browser metadata, traffic source, and engagement duration captured as part of Submissions or analytics events; visitor identifiers stored client-side to deduplicate form view and engagement counts. - **IP addresses and security logs** — IP addresses are processed transiently at the edge (Cloudflare) for rate limiting, bot protection through Cloudflare Turnstile, and abuse prevention. IP addresses are not persisted alongside Submission content in Formstep’s primary database. Short-lived security and abuse logs may retain IP addresses for the limited period needed to operate those defences. - **Integration data** — identifiers, tokens, and payloads exchanged with third-party services that Customer connects. - **AI input/output** — prompts, form structure, and Submission samples that Customer explicitly passes as context, plus the generated outputs, where Customer uses AI Features. Formstep is not designed for, and Customer must not submit, special categories of data under Article 9 GDPR, criminal-conviction data under Article 10 GDPR, government identifiers used for primary identity proofing, payment card data outside the Payment field, or other regulated data classes listed in [Section 6 of the Terms of Service](https://formstep.io/docs/legal/terms-of-service/#acceptable-use), unless Formstep has agreed otherwise in writing. If Formstep becomes aware that Customer has submitted prohibited data classes, Formstep may notify Customer and delete the data. Customer is responsible as Controller for ensuring a valid legal basis for all Personal Data submitted to the Service. ## 6. Customer Obligations Customer is responsible for its role as Controller. In particular, Customer: - Determines a lawful basis under Article 6 GDPR (and, where applicable, Article 9 GDPR) for each Processing activity carried out through the Service; - Provides Data Subjects with all required notices, including a privacy notice covering Customer’s use of Formstep, and obtains any required consent; - Respects Data Subject rights under Articles 15–22 GDPR for the Personal Data Customer processes; - Sets up the Service appropriately, including retention rules, sharing settings, integrations, and access controls; - Does not upload Personal Data the Service is not designed for, as set out in [Section 5](#categories) and [Section 6 of the Terms of Service](https://formstep.io/docs/legal/terms-of-service/#acceptable-use); - Keeps Workspace member access lists, API keys, OAuth credentials, and MCP tokens up to date and revokes them when no longer needed; - Documents its own Processing activities under Article 30 GDPR and carries out data protection impact assessments where required under Article 35 GDPR. Customer warrants that it has the right to give Formstep the instructions set out in this DPA and that all Personal Data has been collected and shared with Formstep lawfully. ## 7. Formstep’s Obligations ### Documented instructions Formstep processes Personal Data only on Customer’s documented instructions, including transfers of Personal Data outside the European Economic Area, unless required to do otherwise by Union or Member State law. Customer’s instructions are set out in this DPA, in the Terms of Service, in the configuration of the Service, and in any further written instructions Customer may give from time to time. Formstep does not process Personal Data for its own marketing purposes, profiling, or any purpose beyond providing and securing the Service as instructed by Customer. If Formstep believes an instruction infringes Applicable Data Protection Law, it will inform Customer and not carry out the instruction. ### Confidentiality Formstep ensures that persons authorised to process Personal Data have committed themselves to confidentiality or are under an appropriate statutory obligation of confidentiality. Access to Personal Data is limited to personnel who need it to perform their duties. ### Security measures Formstep implements appropriate technical and organisational measures to protect Personal Data against accidental or unlawful destruction, loss, alteration, unauthorised disclosure, or access, taking into account the state of the art, the costs of implementation, and the nature, scope, context, and purposes of Processing. The current measures are described in [Annex 2](#annex-2). ### Assistance Taking into account the nature of the Processing and the information available to it, Formstep will assist Customer, by appropriate technical and organisational measures, in fulfilling its obligations to: - Respond to Data Subject requests under Articles 15–22 GDPR (see [Section 8](#data-subjects-rights)); - Maintain the security of Processing (Article 32 GDPR); - Notify Personal Data breaches (Article 33 GDPR) — see [Section 11](#breach); - Communicate breaches to Data Subjects where required (Article 34 GDPR); - Carry out data protection impact assessments (Article 35 GDPR); and - Consult with supervisory authorities where required (Article 36 GDPR). Where this assistance materially exceeds what is available through standard product features, Formstep may charge a reasonable fee, confirmed with Customer in writing (including by email) before work begins. ## 8. Data Subject Rights The Service is designed so that Customer can respond to most Data Subject requests directly, without Formstep’s involvement. Customer can: - View, export, and delete Submissions from the Submissions area of any Form; - View and delete a single Request from the Requests area or the API, which deletes its Submission and the data Customer sent with it; - Set a per-Form retention period, after which Submissions and ended Requests are deleted automatically; - Export Workspace data, including Forms and Submissions, in CSV or Excel (XLSX) — these structured, machine-readable formats also satisfy Article 20 GDPR data portability requests; - Restrict or delete a Workspace member’s access; - Delete entire Forms, Workspaces, or the account, subject to the retention rules in the [Privacy Policy](https://formstep.io/docs/legal/privacy-policy/). Formstep does not carry out automated decision-making or profiling on Customer’s Personal Data within the meaning of Article 22 GDPR. If a Data Subject contacts Formstep directly with a request relating to Personal Data processed for Customer, Formstep will redirect the Data Subject to Customer where possible and will not respond on Customer’s behalf. ## 9. Subprocessors Customer authorises Formstep to engage the Subprocessors listed in [Annex 1](#annex-1) for the Processing of Personal Data. Formstep will: - Enter into a written agreement with each Subprocessor that imposes data protection obligations no less protective than those set out in this DPA, including, where applicable, the SCCs; - Remain fully liable to Customer for the performance of each Subprocessor’s data protection obligations, in accordance with Article 28(4) GDPR. Formstep will give Customer reasonable advance notice before a new Subprocessor begins Processing Personal Data. Customer may object to a new Subprocessor on reasonable, documented data protection grounds by writing to within a reasonable period. If Formstep cannot reasonably accommodate Customer’s objection, Customer may terminate the affected Processing. ## 10. International Transfers Formstep is established in Norway, which is part of the European Economic Area (EEA). No transfer safeguard is required for flows between Formstep and Customers located within the EEA. Several of Formstep’s Subprocessors are established in the United States or process Personal Data on infrastructure located outside the EEA or the United Kingdom. For each such international transfer, Formstep relies on one or more of the following safeguards: - An **adequacy decision** by the European Commission or a competent authority, where one exists for the destination country; - The **Standard Contractual Clauses** adopted by the European Commission in [Decision (EU) 2021/914](https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A32021D0914) (as extended to the EEA by EEA Joint Committee Decision), incorporated by reference into this DPA, with the module that applies depending on the transfer in question (Module 2 — Controller to Processor — for Customer’s transfer to Formstep; Module 3 — Processor to Processor — for Formstep’s onward transfer to Subprocessors that act as processors). Where a Subprocessor (such as Polar or Stripe) collects billing or payment data **directly** from buyers or respondents as an independent controller, that collection is outside Formstep’s processor chain and the Subprocessor’s own transfer mechanisms apply between the data subject and the Subprocessor; - The **UK International Data Transfer Addendum** issued by the UK Information Commissioner (in force March 2022), where Personal Data is subject to the UK GDPR; - The **Standard Contractual Clauses** as recognised by the Swiss Federal Data Protection and Information Commissioner (FDPIC), with the Swiss-specific adaptations set out in the FDPIC’s guidance of 27 August 2021 (clarifying that references to “Member State” must not be interpreted to exclude Switzerland, designating the FDPIC as the competent supervisory authority, and extending the scope to cover personal data of legal entities), where Personal Data is subject to the Swiss Federal Act on Data Protection (nFADP); - The **EU–US Data Privacy Framework** (DPF), its UK extension, and the **Swiss–US Data Privacy Framework**, where the specific US-based Subprocessor is self-certified under the applicable framework (Formstep falls back to SCCs where certification is absent or lapses); and - **Supplementary technical and organisational measures** as described in [Annex 2](#annex-2). Where Formstep relies on SCCs for a transfer to a country without an adequacy decision, Formstep considers the legal framework in the destination country and applies the supplementary measures in [Annex 2](#annex-2) to address identified risks. Where SCCs are the applicable transfer safeguard, by accepting this DPA Customer is deemed to have signed the SCCs as the data exporter and Formstep signs them as the data importer. For the purposes of Annex I to the SCCs: (a) the parties are identified in [Section 1](#parties) of this DPA; (b) the description of the transfer — categories of data subjects, categories of personal data, frequency of transfer, nature and purpose of processing, and retention period — is set out in [Sections 4](#subject-matter) and [5](#categories) of this DPA; and (c) the competent supervisory authority is the Norwegian Data Protection Authority (Datatilsynet). This DPA thereby satisfies the Annex I requirements of the SCCs. The SCCs apply only to transfers that require them. In the event of a conflict between this DPA and the SCCs, the SCCs prevail to the extent of the conflict. ## 11. Personal Data Breaches Formstep will notify Customer without undue delay after becoming aware of a Personal Data Breach affecting Customer’s Personal Data. The notification will include, to the extent known at the time, the nature and scope of the breach, the likely consequences, and the measures Formstep has taken or proposes to take. Where complete information is not available at the time of the initial notification, Formstep will provide it in stages without undue delay. Customer is responsible for keeping its contact details up to date and for notifying its own Data Subjects and supervisory authorities where required. Notifying Customer is not, by itself, an admission by Formstep of fault or liability for any breach. ## 12. Return and Deletion of Data Customer can export Forms and Submissions in standard formats at any time while its account is active, as described in the [Terms of Service](https://formstep.io/docs/legal/terms-of-service/). On termination of the Service or written request by Customer, Formstep will delete all Personal Data Processed on Customer’s behalf, unless Union law, Member State law, or applicable Norwegian law requires further storage. Customer is responsible for exporting any data it wishes to keep before closing the account, using the in-product tools described in [Section 8](#data-subjects-rights). Specifically: - When a Form or Workspace is deleted from the Service, the underlying records are removed from active systems and become unreachable through the application; - When a Customer account is closed, the account’s Personal Data is removed from active systems. Customer is responsible for exporting any data it wishes to keep before closing the account; - Personal Data is removed from backups when the corresponding backup window expires. File uploads stored in Cloudflare R2 are not covered by object versioning; deletions of file uploads are unrecoverable immediately; - Draft and partial responses are retained for a limited period (or until the Data Subject completes the Submission), after which they are automatically purged from active systems. Requests that expired or were canceled without a Submission are purged on the same period, counted from when they ended; - Where Customer has set a retention period for a Form, Submissions and ended Requests are deleted from active systems once it passes; - Personal Data that Formstep is required to retain by law — such as billing records or records relating to a legal claim — will be archived in restricted-access systems for the period required by law. ## 13. Audits Formstep will make available to Customer all information reasonably necessary to demonstrate compliance with its obligations under Article 28 GDPR and this DPA, where Customer has justifiable grounds — such as a confirmed Personal Data Breach, a material breach of this DPA, or a request from a supervisory authority. By default, this information takes the form of: - This DPA and the [Privacy Policy](https://formstep.io/docs/legal/privacy-policy/) in Formstep’s documentation; - The list of Subprocessors in [Annex 1](#annex-1) and the technical and organisational measures described in [Annex 2](#annex-2); - Written responses to reasonable, specific questions sent to . Where a supervisory authority requires an on-site inspection, Formstep will cooperate to the extent required by law. ## 14. Liability The liability of each party under this DPA is subject to the limitations and exclusions set out in [Section 25 (Limitation of Liability)](https://formstep.io/docs/legal/terms-of-service/#limitation-of-liability) of the Terms of Service. The aggregate liability of Formstep under or in connection with this DPA forms part of, and is not in addition to, the overall liability cap set out in the Terms of Service. Notwithstanding the foregoing financial cap, nothing in this DPA limits any liability that cannot be limited under mandatory Applicable Data Protection Law, including liability towards a Data Subject under Article 82 GDPR. The Article 82 carve-out operates regardless of the aggregate cap in the Terms of Service. Formstep is not liable under Article 82 GDPR to the extent it demonstrates it is not in any way responsible for the event giving rise to the damage, in accordance with Article 82(3) GDPR. ## 15. Term and Termination This DPA enters into force when Customer first submits Personal Data to the Service, and no later than the date Customer accepts the Terms of Service. It remains in effect for as long as Formstep processes Personal Data on Customer’s behalf. This DPA terminates when the Terms of Service terminate, provided that it continues in force until Formstep has completed the return or deletion of all Personal Data pursuant to [Section 12](#return-deletion). The provisions of [Sections 5 (Categories)](#categories), [7 (Formstep’s Obligations)](#formstep-obligations) (including the confidentiality obligations therein), [9 (Subprocessors)](#subprocessors), [11 (Breach)](#breach), [12 (Return and Deletion)](#return-deletion), [13 (Audits)](#audits), [14 (Liability)](#liability), and [16 (Governing Law)](#governing-law) survive termination to the extent necessary to give effect to them. ## 16. Governing Law and Jurisdiction This DPA is governed by the laws of Norway, without regard to its conflict of laws principles. Any dispute arising out of or in connection with this DPA is subject to the same dispute resolution and jurisdiction terms as the Terms of Service, except where Applicable Data Protection Law mandates a different forum. Where the SCCs apply, the governing law and forum specified in the SCCs prevail to the extent of any conflict. ## 17. Updates to This DPA Formstep may update this DPA from time to time to reflect changes in the Service, in Applicable Data Protection Law, or in the supervisory authorities’ guidance. - **Non-material changes** (formatting, clarifications that do not alter obligations, updates to Annex 1 that follow the [Section 9](#subprocessors) notice process) take effect upon publication. - **Material changes** (any change that alters the scope of Processing, reduces the level of protection afforded to Personal Data, or modifies the parties’ obligations) take effect after reasonable advance notice. Continued use of the Service after the notice period constitutes acceptance. ## 18. Contact For any matter relating to this DPA, including data subject requests that cannot be handled through the Service, subprocessor objections, audit requests, or breach notifications, contact us at: - **DPA enquiries, data subject requests, and security reports** — Formstep has not appointed a Data Protection Officer. Under Article 37 GDPR, a DPO is required for public authorities and bodies (excluding courts), or where core activities consist of large-scale, regular, and systematic monitoring of individuals, or large-scale processing of special categories of data (Article 9) or criminal-conviction data (Article 10); Formstep’s core activities do not meet any of these thresholds. All DPA-related matters are handled through the contact point above. ## Annex 1 — Subprocessors Formstep engages the following parties in connection with the Service. Convex, Cloudflare R2, Cloudflare, Amazon Web Services (AWS), Axiom, and Unsplash act as data processors under Formstep’s instruction and are true subprocessors for purposes of Article 28 GDPR. Polar and Stripe are listed here for transparency, but they act as **independent controllers** for the data they collect directly from buyers and respondents respectively; Formstep’s Article 28 obligations do not apply to the data those parties control, and their own transfer mechanisms govern where applicable. Google provides optional OAuth sign-in: users authenticate directly with Google, which acts as an independent controller for the Google account; Formstep receives only the basic sign-in profile (name, email, profile picture). PostHog and Affonso are listed for transparency: Formstep uses them for analytics on its own website and app and for its affiliate program on its website and account sign-ups, as controller of that data, and they receive no Customer Personal Data. | Subprocessor | Purpose | Region | | - | - | - | | Convex | Application database and backend infrastructure (hosted on AWS in the eu-west-1 region in Ireland; AWS acts as Convex's infrastructure sub-processor). Convex, Inc. is established in the United States. | European Union (AWS eu-west-1, Ireland) | | Cloudflare R2 | Object storage for file uploads and signatures. Transfer mechanism: Module 3 SCCs under Cloudflare's Data Processing Addendum, supplemented by the measures in Annex 2. | European Union (Cloudflare EU jurisdiction). Cloudflare guarantees that the files are stored within the EU. | | Cloudflare | CDN, DNS, edge security, custom hostnames for hosted forms, Turnstile bot protection. Processes IP addresses transiently for bot protection and abuse prevention. | Global | | Polar | Subscription billing and AI credit purchases. Acts as Merchant of Record and as an independent controller for the buyer billing data it collects. | United States | | Stripe | Respondent payment processing via Stripe Connect (when Payment fields are used). Acts as an independent controller for payment processing. Customer, as the merchant of record for respondent payments, is responsible for ensuring an appropriate transfer mechanism between Customer and Stripe. | Global; region depends on Stripe's processing for the connected account | | Amazon Web Services (AWS) | Amazon Bedrock for AI inference behind AI Features (form building, AI chat, AI Validation, translations), running Claude models made by Anthropic. Anthropic does not receive Customer data. Processing governed by the AWS Service Terms; AWS does not store inputs or outputs after processing and does not use them to train any models. | European Union (Amazon Bedrock cross-region inference within the EU) | | Amazon Web Services (AWS) — Amazon SES | Delivery of all transactional and system email: submission notifications, respondent confirmation and notification emails, magic-link logins, abandoned-response reminder emails, and other account emails. Custom sending domains are verified as SES domain identities (DKIM). Processing governed by the AWS Data Processing Addendum (SCCs incorporated). | Same AWS region as Formstep's AI inference (see the Amazon Web Services entry above) | | Axiom | Operational logging and monitoring of the Service. Logs can contain account and record identifiers and the email addresses of email recipients, including respondents. Logs are kept for 30 days. Axiom, Inc. is established in the United States. | European Union (AWS eu-central-1, Frankfurt) | | PostHog | Analytics for Formstep's own website, app and account sign-ups, including session replays of website visits and of how account holders use the app. Replays of the signed-in app show Formstep's own interface text, such as its buttons and menus. All other text is masked, the places that show forms, Submissions and respondents' details are masked whole, and images are left out, so replays hold no Submissions or other Customer Personal Data. Also receives the product steps an account takes (such as creating or publishing a form), as record identifiers without content, and error reports from the app with the text an error message quotes removed. Receives Formstep account identifiers and visit data; no Customer Personal Data. PostHog, Inc. is established in the United States. | European Union (PostHog EU Cloud, AWS eu-central-1, Frankfurt) | | Affonso | Formstep's affiliate program: records visits that arrive through a partner's link and, through Formstep's Polar account, the subscriptions they lead to, so the partner is credited. Receives website visit data and the billing details of referred subscriptions; no Customer Personal Data. Affonso is operated by ZASolution, established in Germany. | European Union (Frankfurt, Germany) | | Unsplash | Image search API used server-side to find and retrieve form cover images. Selected images are fetched by Formstep at publish time and stored in Cloudflare R2; respondents load images from R2, not directly from Unsplash. Only image URLs and search queries are sent to Unsplash — no Customer Personal Data is transmitted. Transfer mechanism: SCCs under Unsplash's API terms. | United States / Global | **Customer-directed integrations.** When Customer enables an integration (Notion, Airtable, Google Sheets, Slack, Discord, Linear, GitHub, webhooks, etc.), Formstep transmits data to that service on Customer’s documented instruction. The third-party service is not a Formstep Subprocessor; Customer is responsible as Controller for ensuring a lawful basis, appropriate data processing agreements, and transfer mechanisms with each service it connects. If Formstep engages a new Subprocessor or adds a new LLM provider, [Annex 1](#annex-1) will be updated in accordance with [Section 9](#subprocessors). The contractual obligations Formstep imposes on each Subprocessor are set out in [Section 9](#subprocessors). ## Annex 2 — Technical and Organisational Measures Formstep implements the following technical and organisational measures to protect Personal Data, taking into account the state of the art, the cost of implementation, and the nature of the Processing. Specific measures may evolve over time, provided that the overall level of protection is not reduced. ### Access control - Role-based access controls within the Service, with workspace-level permissions for owners and members; - Passwordless authentication using magic-link email or Google OAuth sign-in (Customer-managed multi-factor authentication is available on the Google account used for sign-in), with rate limiting on the login endpoints; anonymous guest sessions (created when a user tries the Service without registering) are purged automatically within a short period; - Internal access to production systems restricted on a need-to-know basis. ### Encryption - TLS 1.2 or higher for all data in transit between Data Subjects, Customers, the Service, and Subprocessors; - Encryption at rest for the primary database, file storage, and backups, using industry-standard ciphers managed by Convex and the underlying cloud provider; - Secrets and API keys stored in encrypted secret stores; passwords are not stored — the Service uses passwordless authentication. ### Logical isolation - Multi-tenant architecture with row-level access scoping by Workspace and Customer; - Logical separation between development, staging, and production environments; production data is not used in non-production environments; - Subprocessor integrations are scoped to the minimum data required to perform their function. ### Resilience and backups - Automated, encrypted daily backups of the primary database (delegated to Convex under its infrastructure service terms); file uploads in Cloudflare R2 are not covered by object versioning; - Use of cloud providers with redundant infrastructure and DDoS protection. ### Vulnerability management - Security updates for application dependencies applied as appropriate. ### Organisational measures - Access to production systems is limited to authorised personnel; - Breach notification procedures aligned with [Section 11](#breach); - Maintenance of records of processing activities in accordance with Article 30(2) GDPR, made available to supervisory authorities on request as required by Article 30(4) GDPR. ### Supplementary measures for international transfers (Schrems II) In addition to the general security measures above, Formstep applies the following supplementary measures for transfers to countries without an adequacy decision, in line with EDPB Recommendations 01/2020: - Encryption in transit (TLS 1.2+) and at rest as described in the [Encryption](#encryption) section; encryption keys are managed by the cloud provider’s key management service and are not accessible to third-country government authorities without a lawful order. ### Data minimisation and retention - The Service collects only the data Customer chooses to collect through Forms and the operational data needed to run the Service. Retention and deletion rules are set out in [Section 12](#return-deletion); - Aggregation is applied to product analytics where the underlying identifiers are not needed for the purpose; - AI inputs and outputs sent to Amazon Bedrock are processed under the [AWS Service Terms](https://aws.amazon.com/service-terms/) and are not stored by AWS after processing. ## Related documents [Privacy Policy](https://formstep.io/docs/legal/privacy-policy/) — How we collect, use, and share personal data. [Terms of Service](https://formstep.io/docs/legal/terms-of-service/) — The rules that govern your use of Formstep. [Legal overview](https://formstep.io/docs/legal/overview/) — Index of Formstep legal documents. # Partner Terms Terms for partners in the Formstep affiliate program: how referrals are credited, how commission and payouts work, and how you may promote Formstep. > **Last updated: 31 October 2026** > > These terms apply to everyone who joins the Formstep partner program. Questions go to . ## 1. The Agreement These Partner Terms (“Partner Terms”) are an agreement between you and Formbase AS, a Norwegian limited company with organisation number 937 921 217 and registered office in Oslo, Norway (“Formstep”, “we”, “us”). You accept them when you create an account in the Formstep partner portal. If you join on behalf of a business, “you” means that business, and you confirm you may bind it. The partner portal is run for us by Affonso. Our [Terms of Service](https://formstep.io/docs/legal/terms-of-service/) still govern any use you make of Formstep itself. ## 2. Joining You must be at least 18 years old and able to receive payouts by one of the methods in [Section 5](#payouts). New partners are approved automatically. We may decline or remove any partner, at any time, including where we believe a partner’s audience or methods do not suit Formstep. ## 3. Your Link and Referrals The portal gives you a referral link. A visitor who arrives through it is credited to you for 30 days. If they click another partner’s link in that time, the last link clicked is credited. A referral counts only when the visitor subscribes to Formstep for the first time, on a plan that earns commission (see [Section 4](#commission)). A visitor who has subscribed to Formstep before, or who uses no referral link, is not a referral. Referral tracking relies on cookies. Where the law requires it, a visitor must accept those cookies first, so some visits through your link cannot be credited. Our records decide whether a subscription is credited to you. ## 4. Commission Commission is earned on Pro subscriptions with monthly billing. Annual plans, the Business plan, AI credit packs and other purchases earn no commission. For each referred customer you earn: - 20% of each payment for the first three months, while the customer has the partner discount (50% off Pro for three months); - 40% of each payment from month four to month twelve. Commission is calculated on the amount the customer actually pays, without tax. No commission is earned on payments made after the twelfth month, or after the customer cancels. We may change commission rates for the future by giving you at least 30 days’ notice. Customers you referred before the change keep the rates that applied when they subscribed for the rest of their twelve months. ## 5. Payouts - Each commission is held for 60 days after the customer’s payment, so refunds and chargebacks can settle first. - We pay monthly in US dollars, by Wise or bank transfer, once your approved balance reaches USD 50. A smaller balance carries over. - We may delay a payout while we review unusual activity, such as a sudden spike in referrals or signs of a breach of these Partner Terms. Commission that passes the review is paid with the next payout. - If a referred customer is refunded, the commission on that payment is cancelled, or deducted from a later payout if it was already paid. - For each chargeback, we deduct the commission plus a USD 15 fee from your future payouts. After two chargebacks we pause your account while we review it. - You must give accurate payout details. Fees charged by your bank or payment provider are yours. - You are responsible for reporting and paying your own taxes on commission. We may ask for an invoice or tax information before paying. ## 6. How You May Promote Formstep You must: - Disclose clearly, next to your link, that you earn a commission when someone subscribes; - Describe Formstep, its prices and the partner discount accurately, and make no promise we do not make; - Follow the advertising, consumer protection, privacy and anti-spam laws that apply to you and your audience. You may list Formstep and the partner discount on your own pages, including deal, perk and startup directories, as long as the listing links through your referral link and describes the offer accurately. You must not: - Bid on “Formstep”, “Formbase” or any close variant or misspelling in search or social ads, or use them in ad copy or display URLs; - Offer visitors cash, cashback, points or any other reward for clicking your link or subscribing; - Send unsolicited messages, or promote Formstep in spam, forums or comments where it is unwelcome; - Use forced clicks, hidden links, pop-unders, cookie stuffing or any method that credits you for a visitor who did not choose to click your link; - Refer yourself, your own company, or accounts you control; - Register domains, social handles or app names that contain “Formstep”, or present yourself as Formstep or as speaking for us; - Promote Formstep next to illegal, hateful, adult or deceptive content. If you break this section, we may cancel commission earned through the breach and end your partnership under [Section 9](#ending). ## 7. Our Brand and Marketing Kit While you are a partner, you may use the Formstep name, logos, screenshots, graphics and copy from our marketing kit to promote Formstep under these Partner Terms. Do not change the logos’ colours or shape, or combine them with your own marks. This permission ends when your partnership ends, and we may ask you to change or remove any material at any time. ## 8. Privacy You see the clicks, conversions and commission your link earns. You do not see who our customers are, and you must not try to identify them. We process your own personal data, such as your name, email and payout details, to run the program and pay you, as described in our [Privacy Policy](https://formstep.io/docs/legal/privacy-policy/). Affonso processes that data for us. ## 9. Ending the Partnership You may leave at any time by closing your partner account or writing to us. We may end your partnership at any time with notice, or at once if you break these Partner Terms. When a partnership ends without a breach, we pay the commission you earned before it ended, once its hold period has passed and the balance reaches the payout minimum. When we end it because of a breach, unpaid commission is cancelled. ## 10. Our Relationship You act as an independent partner. Nothing in these Partner Terms makes you our employee, agent or representative, and you may not make commitments on our behalf. The program is not exclusive: you may promote other products, and we may work with other partners. ## 11. Liability The partner program is provided as it is. We do not promise any level of referrals or earnings, or that tracking is always uninterrupted. To the extent the law allows, our total liability to you under these Partner Terms is limited to the commission paid to you in the twelve months before the claim. You are responsible for your own promotion and for any claims that arise from it. ## 12. Changes We may update these Partner Terms. For a material change, we email you at least 30 days before it takes effect. If you keep promoting Formstep after that, you accept the updated terms. ## 13. Governing Law Norwegian law governs these Partner Terms. The Oslo District Court (Oslo tingrett) has jurisdiction to settle any dispute arising out of or in connection with them. ## Related documents [Terms of Service](https://formstep.io/docs/legal/terms-of-service/) — Rules governing your access to and use of Formstep. [Privacy Policy](https://formstep.io/docs/legal/privacy-policy/) — How we collect, use, and share personal data. [Legal overview](https://formstep.io/docs/legal/overview/) — Index of Formstep legal documents.