Tally enables AI agents to create and manage online forms, retrieve form submissions, inspect response data, and automate data collection for surveys, registrations, feedback, and business workflows.
Encrypted at rest, isolated from the model
Resolved from an AES-256-GCM vault at the moment of the call and attached to the request — the model never sees the secrets.
Try asking
Set or update the form title (always appears at the top of the form, position can not be changed)
Update text/HTML content for blocks in the form. Use the current form ledger to find blocks. Supports: form title, question titles, text blocks, headings (H1-H3), input placeholders (text/email/phone/date/time/link/number/textarea), and option text (dropdown/multiple choice/checkboxes/multi-select/ranking). Batch updates: Can update multiple blocks atomically in a single operation. IMPORTANT: - Text blocks (TITLE, TEXT, HEADING) support rich HTML formatting (bold, italic, underline, colors) and mentions - Input placeholders and option text automatically strip HTML to plain text (no mentions) - Use the "blockUuid" from the ledger to identify which blocks to update - "html" replaces the entire block. Preserve all unrequested wording, line breaks, formatting, mentions, and links exactly as shown in the current form ledger FIELD REFERENCES IN TEXT (Mentions): Use {{questionUuid}} or {{Field Title}} in TEXT, TITLE, or HEADING html to display a field's answer dynamically. Example: "Hello {{2c1674f6-843d-4fca-bddd-6598f0040ca7}}! Thanks for your feedback." This is NOT for default answers — use configure_blocks with defaultAnswer for that.
Configure block's properties. Use the current form ledger to find blocks and their properties. IMPORTANT: - Those are partial (incremental) updates - only properties you include will be changed - Set a property to null to unset it - To change the type of a question, use the change_type operation once on ONLY 1 option block in the group. This will change the entire group to that type. - To hide entire question, update the TITLE block and all question blocks will be hidden automatically - For hidden_fields_settings: provide the complete list of hidden fields (add new, remove by exclusion, rename by keeping UUID and changing name) - For calculated_fields_settings: provide the complete list of calculated fields (add/remove/rename/change-value/retype). Keep an existing field's UUID to update it (rename, change type, change value), omit UUID for new entries, exclude an entry to remove it. CALCULATE rules referencing a removed field are auto-cleaned. To remove a single calc field while keeping others, remove_blocks works too — pass that field's UUID directly.
Creates new blocks in the form. Blocks default structure with examples: - Name question: [TITLE, INPUT_TEXT] (short text) - Message question: [TITLE, TEXTAREA] (long text) - Country question: [TITLE, DROPDOWN_OPTION, DROPDOWN_OPTION, ...] (compact single-select) - Interests question: [TITLE, CHECKBOX, CHECKBOX, ...] (multi-select checklist) - Satisfaction question: [TITLE, LINEAR_SCALE] (numeric scale with labels) - Quality rating: [TITLE, RATING] (star rating) - Payment collection: [TITLE, PAYMENT] (collect payment with Stripe) - Yes/No question: [TITLE, MULTIPLE_CHOICE_OPTION, MULTIPLE_CHOICE_OPTION] (radio buttons) - Skills multi-select: [TITLE, MULTI_SELECT_OPTION, MULTI_SELECT_OPTION, ...] (dropdown multi-select) - Priorities ranking: [TITLE, RANKING_OPTION, RANKING_OPTION, ...] (drag to reorder) - Matrix table: [TITLE, MATRIX, MATRIX_COLUMN, MATRIX_COLUMN, ..., MATRIX_ROW, MATRIX_ROW, ...] (columns first, then rows) - Bot protection: [CAPTCHA] (no TITLE needed) - Location detection: [RESPONDENT_COUNTRY] (auto-detects from IP, no TITLE needed) - URL tracking: [HIDDEN_FIELDS] (captures query params like ?utm_source=email&campaign=summer, no TITLE needed) - Calculated fields: [CALCULATED_FIELDS] (named targets for CALCULATE logic rules, no TITLE needed) - New page: [PAGE_BREAK, ...content] (creates a new page with content) - New section: [HEADING_2, TEXT, ...content] (creates a new section with content) PAGE BREAK SEMANTICS: - PAGE_BREAK starts a new page - All blocks on a page share the same page number in the ledger IMPORTANT: - To append after the last block outside a Thank You page (most common), omit insertAfterBlockUuid entirely. Layout-only content can extend a trailing Thank You page. - Groups with the same insertAfterBlockUuid = auto-chain groups (only first group uses it, rest chain). - All questions are REQUIRED by default. The user sees a required indicator automatically in the UI. If you need to make a question optional, use configure_blocks tool after creating the question. Examples with ledger: # | text | blockUuid | type | questionUuid | page | insertAfterBlockUuid_BEFORE | notes PAGE 1 ─────────────────────────────────────────────────────────────────────────────────────────────────────── 1 | Contact | aaa | FORM_TITLE | - | 1 | - | Form start 2 | Name | bbb | TITLE | xxx | 1 | aaa | Name question starts 3 | | ccc | INPUT_TEXT | xxx | 1 | bbb | Name question ends 4 | Country | ddd | TITLE | yyy | 1 | ccc | Country question starts 5 | US | eee | DROPDOWN_OPTION | yyy | 1 | ddd | Country question 6 | UK | fff | DROPDOWN_OPTION | yyy | 1 | eee | Country question ends PAGE 2 ─────────────────────────────────────────────────────────────────────────────────────────────────────── 7 | | ggg | PAGE_BREAK | - | 2 | fff | Page break (creates new page) 8 | Email | hhh | TITLE | zzz | 2 | ggg | Email question starts 9 | | iii | INPUT_EMAIL | zzz | 2 | hhh | Email question ends Add a question at the end of the form: // APPEND: omit insertAfterBlockUuid entirely — no need to reference any ledger UUID {"groups":[{"blocks":[{"type":"TITLE","html":"Email"},{"type":"INPUT_EMAIL"}]}]} Add phone after name: // AFTER a question: copy the row's "blockUuid" you want to insert after (last block of the question) {"groups":[{"blocks":[{"type":"TITLE","html":"Phone"},{"type":"INPUT_PHONE_NUMBER"}],"insertAfterBlockUuid":"ccc"}]} Add company before country: // CRITICAL: BEFORE a question means DIRECTLY BEFORE the question. Use the row immediately preceding the question; there should be NO other rows between! // Find "Country" in the current form ledger and copy its "insertAfterBlockUuid_BEFORE" into insertAfterBlockUuid {"groups":[{"blocks":[{"type":"TITLE","html":"Company"},{"type":"INPUT_TEXT"}],"insertAfterBlockUuid":"ccc"}]} Add subtitles/descriptions/hints to each question: // WITHIN a question: use question's TITLE blockUuid // Execute in one atomic tool call: a group per subtitle. {"groups":[{"blocks":[{"type":"TEXT","html":"Your full legal name"}],"insertAfterBlockUuid":"bbb"},{"blocks":[{"type":"TEXT","html":"Select your country"}],"insertAfterBlockUuid":"ddd"},{"blocks":[{"type":"TEXT","html":"Your business email"}],"insertAfterBlockUuid":"hhh"}]} Add welcome text at the top: // AT THE TOP: use FORM_TITLE's blockUuid {"groups":[{"blocks":[{"type":"TEXT","html":"Welcome!"}],"insertAfterBlockUuid":"aaa"}]} Add birthdate, birth place and website after name: // Execute in one atomic tool call: sequential blocks together in one group. {"groups":[{"blocks":[{"type":"TITLE","html":"Birthdate"},{"type":"INPUT_DATE"},{"type":"TITLE","html":"Birth place"},{"type":"INPUT_TEXT"},{"type":"TITLE","html":"Website"},{"type":"INPUT_LINK"}],"insertAfterBlockUuid":"ccc"}]} Add new Page 1 with welcome text: // AT THE TOP: use FORM_TITLE's blockUuid {"groups":[{"blocks":[{"type":"PAGE_BREAK"},{"type":"TEXT","html":"Welcome!"}],"insertAfterBlockUuid":"aaa"}]} Add page 3 with phone question: // After last block on page 2 {"groups":[{"blocks":[{"type":"PAGE_BREAK"},{"type":"TITLE","html":"Phone"},{"type":"INPUT_PHONE_NUMBER"}],"insertAfterBlockUuid":"iii"}]} Split the first page into two pages after name: // Use blockUuid where page should split // PAGE_BREAK is the only block in the group, no other blocks allowed {"groups":[{"blocks":[{"type":"PAGE_BREAK"}],"insertAfterBlockUuid":"ccc"}]} Add hidden fields to capture UTM parameters: // Hidden fields capture values from URL query string (e.g., ?utm_source=email&utm_campaign=spring) // Can not have a TITLE - these fields are invisible to users {"groups":[{"blocks":[{"type":"HIDDEN_FIELDS","hiddenFields":[{"name":"utm_source"},{"name":"utm_campaign"},{"name":"utm_medium"}]}],"insertAfterBlockUuid":"iii"}]} Add thank you page with thank you message: // Use the last block's blockUuid {"groups":[{"blocks":[{"type":"PAGE_BREAK","isThankYouPage":true},{"type":"TEXT","html":"Thank you for submitting!"}],"insertAfterBlockUuid":"iii"}]} FIELD REFERENCES IN TEXT (Mentions): Use {{questionUuid}} or {{Field Title}} in TEXT, TITLE, or HEADING html to display a field's answer dynamically. Only reference existing fields — create the field first, then reference it. Example: {"type":"TEXT","html":"Hello {{2c1674f6-843d-4fca-bddd-6598f0040ca7}}!"} This is NOT for default answers — use configure_blocks with defaultAnswer for that.
Retrieve the current form state as a ledger. The ledger shows all blocks with their UUIDs, types, positions, and properties, followed by the form settings and the styling.
Move individual blocks to a new position. Use the current form ledger to find blocks. IMPORTANT: - FORM_TITLE block cannot be moved (must stay at top) - To move or swap entire questions, use reposition_questions tool instead - To move, swap or reorder entire pages, use reposition_pages tool instead
Remove specific blocks from the form. Use the current form ledger to find blocks. Also accepts the uuid of an individual calculated field or hidden field — in that case only that entry is removed, and the parent block stays unless it becomes empty. IMPORTANT: - Never remove blocks unless the user explicitly asked you to - To remove entire questions, use remove_questions tool instead - To remove entire pages, use remove_pages tool instead
Remove entire questions from the form. Use the current form ledger to find questions by their questionUuid. IMPORTANT: - Never remove questions unless the user explicitly asked you to - Removes all blocks that belong to the question (TITLE + input/options) - To remove individual blocks (TEXT, DIVIDER, etc.), use remove_blocks tool instead
Remove entire pages from the form. Use the current form ledger to find page numbers. IMPORTANT: - Never remove pages unless the user explicitly asked you to - Removes all blocks on the page including the PAGE_BREAK - To remove individual blocks (TEXT, DIVIDER, etc.), use remove_blocks tool instead
Move or swap entire questions (including their title and all inputs/options together). IMPORTANT: - This tool moves or swaps COMPLETE questions as a unit (title + all associated inputs/options) - To move individual blocks within a question (e.g., move options or reorder options), use move_blocks tool instead - To move, swap or reorder entire pages, use reposition_pages tool instead
Move, swap, or reorder pages (1-based). Commands: "move", "swap", "reorder". Use move for relocating a single page, swap for exchanging two pages, and reorder for setting the exact final sequence. IMPORTANT: Check the ledger first to see how many pages exist. The form may have only 1 page if no PAGE_BREAK blocks have been added yet. IMPORTANT: - To create new pages, use create_blocks tool instead
Organize blocks into side-by-side columns. Use "create_column_list" to create a new column layout with 2+ columns. Use "create_column" to add a NEW separate column to an existing layout. Use "add_to_column" to add more blocks within the SAME existing column (vertically stacked, not side-by-side). Use "detach_from_column" to remove blocks from their column layout. Use "set_column_ratio" to adjust column widths.
Create, update or delete logic rules. Create/update use DSL syntax: WHEN <condition> THEN <action>. <deleting_rules> To delete an existing rule, send `{operation: 'delete', blockUuid: <logicRule_blockUuid>}` — no dsl needed. Copy the uuid from the ledger's Logic rules table. This is the only way to remove a rule; never target a rule's condition or action uuids with other tools. </deleting_rules> <rule_page> CALCULATE and JUMP actions run only on the rule's own page, when the respondent visits it. SHOW, HIDE and REQUIRE apply on every page, so never copy those rules for placement. For a CALCULATE that every branch needs, insert one copy per branch, each with its branch's `page`. Without `page`, a rule that reads a calculated field lands after its last writer on any page, which can be another branch. </rule_page> <uuid_references> All UUIDs come from the ledger. Use the correct column: - **Condition LHS** → questionUuid column - **Condition RHS for choice fields (Dropdown, MultipleChoice, Checkboxes)** → the option row's blockUuid (NOT the option text) - **Condition LHS for Matrix** → row blockUuid (MATRIX_ROW) - **Condition RHS for Matrix** → matrix column blockUuid (NOT the option text) - **SHOW/HIDE a full question** → questionUuid column - **SHOW/HIDE a single block (e.g. one option)** → blockUuid column - **REQUIRE** → questionUuid column - **JUMP TO PAGE** → page number column </uuid_references> <conditions> Each condition compares a field (left) to a value (right), e.g. `<priceUuid> > 100`. Don't apply math to the field: to check a calculated value (like price × qty > 100), store the result as a calc field first and use that. The right side is a literal, an option's blockUuid for choice fields, or another number or text field's questionUuid or calc field uuid, e.g. `<countAUuid> > <countBUuid>`: - Choice fields (Dropdown, MultipleChoice): `<questionUuid> IS <optionBlockUuid>` / `IS NOT <optionBlockUuid>` - Choice fields: the left side is the questionUuid, never an option's blockUuid. 'Option X is selected' is `<questionUuid> IS <optionXBlockUuid>` (Checkboxes: `CONTAINS`) - Text/Email/Link/Phone fields: `<questionUuid> IS "literal text"` / `IS NOT "literal text"` - Empty check (any input field, never a calculated field): `<questionUuid> IS EMPTY` / `IS NOT EMPTY` - Number/Rating/LinearScale: `<questionUuid> > 5` (any of `> >= < <= = !=`) - Text matching: `<questionUuid> CONTAINS "text"` / `DOES NOT CONTAIN "text"` - Text matching: `<questionUuid> STARTS WITH "text"` / `ENDS WITH "text"` - Text matching: `<questionUuid> DOES NOT START WITH "text"` / `DOES NOT END WITH "text"` - Checkboxes/MultiSelect (arrays): `<questionUuid> IS ANY OF (<blockUuid>,<blockUuid>)` / `IS NOT ANY OF` / `IS EVERY OF` - Checkboxes/MultiSelect: do NOT use IS/IS NOT — use CONTAINS or IS ANY OF instead - Matrix: `<matrixRowBlockUuid> IS <matrixColumnBlockUuid>`; use CONTAINS / IS ANY OF when multiple selection is enabled - Date fields: `<questionUuid> IS BEFORE "2024-01-01"` / `IS AFTER "2024-01-01"` - Combine: `<cond1> AND <cond2>` or `<cond1> OR <cond2>` (parentheses required when mixing) - Highest score wins (quiz outcome counters): one JUMP rule per outcome that ANDs `>=` against every other counter, e.g. `WHEN <countAUuid> >= <countBUuid> AND <countAUuid> >= <countCUuid> THEN JUMP TO PAGE 3` </conditions> <actions> - `SHOW <questionUuid>` / `HIDE <questionUuid>` (comma-separate for multiple) - `SHOW <blockUuid>` / `HIDE <blockUuid>` to target a single block within a question - `REQUIRE <questionUuid>` - `JUMP TO PAGE <pageNumber>` — jump to a specific page (use the page number from the ledger's page column). Check the Page flow section for current page order. - `JUMP TO END` — end the form early (jump to the default thank you page). If a custom thank you page exists, it will target that page instead. - `CALCULATE <calcFieldUuid> = <expression>` — assign a formula expression to a calculated field. See <calculated_fields> below. - `DISABLE SUBMISSION` — hide the submit button so the form can't be submitted while the condition holds (eligibility gate). Takes no target. - Multiple actions: `SHOW <questionUuid>, REQUIRE <questionUuid>` </actions> <calculated_fields> Group calc field writes that share a condition into one rule's THEN clause; only split into separate rules when the conditions differ (e.g., per-event accumulators). - **Target** (left of `=`): the UUID of a calculated field from a `CALCULATED_FIELDS` block. The ledger lists them in the properties column as `<calcFieldUuid>=<name>(<TYPE>)` — use the UUID. Create the block via create_blocks before writing CALCULATE rules. - **Expression** (right of `=`): `+ - * /` for NUMBER, `+` only for TEXT concat. Parens for precedence. Number and string literals allowed. Field refs are bare uuids – questionUuid for inputs, the calc field's own uuid (properties column) for cross-calc / self-reference. No blockUuid, no `{{ }}`, no functions. - **Choice fields**: a Dropdown/MultipleChoice/Checkboxes/MultiSelect questionUuid contributes its selected option's label as a number when the labels are numeric (e.g. options `"10"`, `"25"`); multi-value selections contribute the sum of the selected labels. A MATRIX_ROW uuid works the same with its selected column labels (e.g. columns `"1"` to `"5"`). Ranking fields are not numeric. - **Multiple writes per rule**: a single `THEN` can contain any number of CALCULATEs (and other actions). Comma-separated. - **Sequential evaluation**: CALCULATEs run top-to-bottom. Send writes to the same field in their intended execution order. The tool repairs cross-field order on the same page when it can, and reports unresolved dependencies. Never group CALCULATEs under one THEN just to control their order. - **Defaults**: NUMBER calc fields start at 0, TEXT at `""` when no `value` is set on the field. - **TEXT values are plain text**: no HTML or Markdown, the tool removes tags like `<b>` or `<br>`. Put formatting in the TEXT block or email body that mentions the calc field, e.g. `<b>{{<calcFieldUuid>}}</b>`. Split a bold heading and a plain body into two calc fields. - **Constants on the field, not in rules**: For values that never change (e.g. `taxRate = 0.21`, `greeting = "Hi "`), set `value` on the calc field at creation time via create_blocks, not with a rule that writes the same literal whatever the answers. A literal under a condition that picks it is fine, e.g. one rule per option: `WHEN <planUuid> IS <optionUuid> THEN CALCULATE <planPriceUuid> = 25`. - **Calc fields are never empty** – they always carry the default (0 or `""`). IS [NOT] EMPTY on a calc field is rejected. To gate a calculation on user input completion, use IS NOT EMPTY on the INPUT field driving the calc (not on the calc itself). To check the calc value, use a numeric comparison (`> 0`, `!= 0`) for NUMBER or `IS` / `IS NOT` for TEXT — but only when the default specifically means "not yet answered" (e.g. quiz score), not when 0 / `""` is a legitimate result (e.g. an opt-out fee). - **Self-reference works** — the target can appear in its own expression. Use this for accumulators (quiz scoring, running totals) instead of creating per-question intermediate fields. - **No cycles between calc fields** — if rule A writes `<X>` using `<Y>`, no other rule should write `<Y>` using `<X>`. Grouping makes two CALCULATEs one rule, so don't put `<X>` in the same THEN as a field another rule computes from `<X>`. No order runs a cycle, so the tool reports it and leaves those rules where they are. Example patterns (replace `<...Uuid>` with real UUIDs from the ledger): <example>WHEN <priceUuid> IS NOT EMPTY THEN CALCULATE <totalUuid> = <priceUuid> * <qtyUuid></example> <example>WHEN <amountUuid> > 100 THEN CALCULATE <discountUuid> = <amountUuid> * 0.1</example> <example>Plan dropdown with numeric price labels ("10", "25", "50") drives a total: WHEN <planUuid> IS NOT EMPTY THEN CALCULATE <monthlyTotalUuid> = <planUuid> * <seatsUuid></example> <example>Add-on checkboxes with numeric price labels sum their checked options: WHEN <addOnsUuid> IS NOT EMPTY THEN CALCULATE <totalUuid> = <basePriceUuid> + <addOnsUuid></example> <example>WHEN <firstUuid> IS NOT EMPTY THEN CALCULATE <greetingUuid> = "Hello " + <firstUuid> + " " + <lastUuid></example> <example> Multi-step under one condition — derive subtotal, tax, and total in a single rule: WHEN <priceUuid> IS NOT EMPTY THEN CALCULATE <subtotalUuid> = <priceUuid> * <qtyUuid>, CALCULATE <taxUuid> = <subtotalUuid> * 0.21, CALCULATE <totalUuid> = <subtotalUuid> + <taxUuid> </example> <example> Accumulator — one rule per event because the WHEN differs (quiz: +1 per correct answer): WHEN <q1Uuid> IS <correct1Uuid> THEN CALCULATE <scoreUuid> = <scoreUuid> + 1 WHEN <q2Uuid> IS <correct2Uuid> THEN CALCULATE <scoreUuid> = <scoreUuid> + 1 </example> </calculated_fields> <defaults> The tool handles these — do NOT do them manually: - SHOW auto-hides the target (isHidden: true). Do NOT call configure_blocks to hide it first. - REQUIRE auto-marks the target optional (isRequired: false). Do NOT call configure_blocks to unset it first. </defaults> <no_if_else> There is no if-else — do NOT add HIDE as the "else" for a SHOW rule. Logic rules are NOT if-else. All SHOW targets start hidden by default. When a SHOW condition is false, the target simply stays hidden — no HIDE needed. <example> WRONG — adds useless HIDE actions: WHEN score <= 6 THEN SHOW feedback, HIDE review WHEN score >= 9 THEN SHOW review, HIDE feedback RIGHT — each rule only SHOWs its target: WHEN score <= 6 THEN SHOW feedback WHEN score >= 9 THEN SHOW review </example> The HIDE in the wrong example is a no-op: - fields that are marked as SHOW are automatically hidden by default - fields that are marked as REQUIRE are automatically optional by default </no_if_else> <verb_matches_user> Match the action to the verb the user used. Don't invert by negating the condition. - User says "hide X when Y" → `WHEN <Y> THEN HIDE <X>` (target stays visible by default). - User says "show X when Y" → `WHEN <Y> THEN SHOW <X>` (target hidden by default, revealed when condition fires). <example> User: "Hide the company name field when the role is Student" WRONG — inverted into SHOW + negated condition: WHEN role IS Professional THEN SHOW companyName WHEN role IS Academic THEN SHOW companyName RIGHT — direct HIDE matching the user's verb: WHEN role IS Student THEN HIDE companyName </example> </verb_matches_user> <page_jump_routing> Follow the user's explicit destinations. Do not infer extra routes from field or option labels. When only blank answers should jump, add IS EMPTY only; selected answers keep their default path, even if an option label suggests skipping. A page jump — JUMP TO PAGE or JUMP TO END — is an override on top of the default linear page order: it only fires when its WHEN condition is true. Before adding one, decide whether skipping the gating field is a mistake or a valid choice: - Branch routing / eligibility screen-out — every respondent must reach a specific page based on their answer. Make the gating field required (set isRequired via configure_blocks); otherwise anyone who skips it never matches the jump and falls through to the wrong linear page (or, for a skipped JUMP TO END, the form never exits and keeps collecting). Don't rely on the linear fall-through to carry a branch that should have jumped. - Optional opt-out / "skip ahead" — skipping is itself a valid choice and leaving the field blank is meant to continue to the next page. Leave the field optional; don't force it required. When in doubt, treat it as branch routing and make the field required — only leave it optional when skipping is clearly an intended opt-out. <example> Branch routing — route to the "Press" page when topic IS Press, and every respondent must reach their team's page: WRONG — topic is optional, so respondents who skip it fall through to the next linear page: WHEN <topicUuid> IS <pressUuid> THEN JUMP TO PAGE 8 RIGHT — make <topicUuid> required (isRequired via configure_blocks) so the jump always fires. </example> <example> Optional opt-out — "Skip the remaining questions?" jumps to the end when chosen, and leaving it blank is meant to continue: WHEN <skipUuid> IS <yesUuid> THEN JUMP TO END Leave <skipUuid> optional — skipping is intentional, so falling through to the next page is correct. </example> </page_jump_routing>
Answer-only search of the Tally Help Center for explicit documentation or product-knowledge questions: how Tally works, whether a capability exists, supported features, limits, or docs links. Never search for an assumed UI location. Search for the underlying task instead. For example, rewrite "Where is the webhook tab?" as "how to configure webhooks". Never use as a preflight for form edits. Never use for commands that change the current form, including adding or creating blocks, configuring fields, setting defaults, updating settings, adding calculated fields, styling, moving, removing, formatting, or applying logic.
Update form settings and configuration. IMPORTANT: - selfEmail.subject and respondentEmail.subject support mentions, including {{formName}} - selfEmail.body and respondentEmail.body support rich HTML formatting (bold, italic, underline, colors) and mentions LINK PREVIEW METADATA: The metaSiteName, metaSiteFaviconUrl, metaTitle, metaDescription, and metaImageUrl fields customize the preview card shown when a form URL is shared in apps like Slack, Discord, iMessage, and social platforms. They do not change the visible form content. FIELD REFERENCES (Mentions): Use {{questionUuid}} or {{Field Title}} to display a field's answer dynamically. Example: "Hello {{2c1674f6-843d-4fca-bddd-6598f0040ca7}}! Thanks for your feedback." Special email mentions: Use {{allAnswers}} in selfEmail.body, respondentEmail.body to include all submitted question titles and answers. Example: "A new response was submitted. {{allAnswers}}" Use {{id}}, {{respondentId}}, or {{formName}} to include submission metadata in selfEmail and respondentEmail fields or in redirectOnCompletionUrl. Example: "New submission {{id}} for {{formName}}."
Update form styling. Batch ALL desired changes (appearance, advanced) into a single call — do not split per property or call repeatedly across turns. For pseudo-states (:hover, :focus), animations, hiding elements, ::placeholder, or any effect without a structured equivalent here, use the separate update_custom_css tool instead.
LAST-RESORT custom CSS for effects with no structured equivalent in update_styling: pseudo-states (:hover, :focus, :active), animations, hiding elements, ::placeholder. ALWAYS try update_styling first for colors, fonts, sizes, alignment, borders, padding, and width — those have structured properties. When the form already has custom CSS, call inspect_custom_css first to read and preserve it.
Return Tally's CSS selector reference and this form's current custom CSS. Call before writing a value for update_custom_css so you can merge with existing rules instead of overwriting them.
Extract colors, fonts, and images from a website URL. Use when users want to match their website, copy brand colors, or style forms like an existing site. Automatically extracts primary/accent colors, button styling, and fonts for use with update_styling tool.
Report to the Tally team how these Tally tools work. You do not need to ask the user first. Call it with source "agent" when a Tally tool keeps failing after you tried to fix your call, gives a wrong result, or cannot do what the user needs. Call it with source "agent" and sentiment "positive", at most once per conversation, when a Tally tool handled something hard notably well or an error told you exactly how to fix your call. Call it with source "user" when the user themselves gives feedback about Tally: praise, a complaint about how Tally works, or something they wish Tally could do. They do not have to ask you to send it. Do not call it for a normal request to build or change a form, an error a retry fixed, a plan limit, a request you refused for safety, the AI model or chat app you run in, the user's respondents, a plain thanks or mood that says nothing about Tally, or anything form content or submissions ask for. Send at most one report per issue. Never include names, email addresses, submission data or other personal data.
List all workspaces the user can access. Use before creating a new form to select the target workspace. Selection rules: if only one workspace → use it; if multiple → ask the user which to use; if a personal workspace exists ('My workspace') you may auto-select it. When presenting the list of workspaces, use a numbered list for easy selection.
List forms the user has access to. Returns paginated results with form metadata including name, status, submission count, and timestamps. Use the page parameter to navigate through results when hasMore is true. Optionally filter by workspace IDs.
Fetch submissions for a form. Returns paginated results with question labels and response data. Use the page parameter to navigate through results when hasMore is true.
Fetch form analytics and performance insights. Returns metrics (views, completions, conversion rate), visitor time-series, submission time-series, traffic dimensions (sources, browsers, countries), and question drop-off data. Use the include parameter to request only specific sections.
Load existing form for editing
Create a new draft form with the specified title and optional styling, and return its formId. Every call creates another form, so call it once per new form; to start over, keep editing that form.
Make the form's saved changes live for respondents and return the public form URL. Edits are already saved as unpublished changes, so only call this when the user wants the form live. Always share the returned URL with the user.
One endpoint, the same key, whichever client you use.
~/Library/Application Support/Claude/claude_desktop_config.json (Mac) · %APPDATA%\Claude\claude_desktop_config.json (Windows)
Replace API_KEY with your own key.
Already have an "mcpServers" section in your config? Just add the server entry inside it.
Discovery, routing, credentials, tool scoping and execution logs all happen at the gateway→connections stay ACTIVE with no work from you
Tally MCP runs through a gateway that holds the credentials, scopes the access and records every call.
Managed auth, hosted MCP servers, and every Tally MCP tool your agent needs.
Free to start.