← Fewer Fields
API

Run Fewer Fields from your own code

The one metered task, trim, is available over HTTP. It takes a web form as the page has read it - the form type, what you say about the form and your business, one numbered line per field (q1, q2 ...) with its kind and input, whether it is required, how its label shows, a rough number of seconds to answer it and whether your team uses the answer, then the page's findings and the button text - and returns one action per listed field (keep, optional, later, merge or drop) with a new label, an error message and a reason, the order of the fields that stay, steps for a long form, a new button, a line to show under it, two or three A/B tests, a verdict (lean, trim or steps) and a note.

The division of labour first: the page reads the form - its HTML, without running it, or a plain list, one field per line - and works out each field's kind, whether it is required, how its label shows (label shown, placeholder only, screen-reader label only, code name only or no label), a rough number of seconds to answer it (a typing-time guess, halved when the field is optional) and the lint findings; the run decides what happens to each listed field and writes the words, naming fields only by id so the page can tie each action to a real field. The page reads up to 300,000 characters and lists up to 30 fields (q1 ... q30); the rest are counted and get no action. After the reply it keeps one known action per listed field, refuses a merge across two different answers or into a field that does not stay, and checks the verdict against the plan itself (steps when more than six fields stay, trim when any field is later, merge or drop, lean otherwise), the order and the steps against the fields that stay, that no field your team uses is dropped, that an email or phone field stays required and that the button is not a bare Submit or Send; it warns when a sentence quotes a number the input does not have or names a field by its id. Nothing is sent anywhere from the page - the shorter form comes back as plain HTML for you to paste into your site. Your code can do the same with the engine files the page loads (see "Build the input yourself").

Base URL and the response shape

Every endpoint lives under https://api.skillsafe.ai/v1/app-api, and every response has one shape:

{ "ok": true,  "data":  { ... } }
{ "ok": false, "error": { "code": "...", "message": "..." } }

The token is minted for this app (the guest call takes {"slug":"fewer-fields"}), so no slug header is needed afterwards. Send it as Authorization: Bearer …. The run body is the input object, sent bare.

StatusCodeMeaning
400validation_errorThe body is not a JSON object, or a required field (task, kind, goal, fields, facts, button) is missing.
401unauthorizedNo token, or a stale one. Mint a guest token or sign in again.
402insufficient_creditsThe balance is under min_credits. Price with /estimate first.
403forbiddenA guest token tried to run: the trim run is metered and needs a personal sign-in.
404not_foundUnknown job id.
429rate_limitedBack off and retry.
5xxserver_errorTransient. Retry with the same Idempotency-Key so a retry never double-bills.

The task field, the input and the rules

Every run sends task: trim (the one task is trim); a missing or different one is still answered as trim. All fields are strings. What each holds, in the words of the system prompt, with the first sample's value (its first line where it has several):

FieldRequiredWhat it holds
taskyesAlways trim. If it is missing or different, still answer this task and set "task": "trim".
kindyesThe form type: lead (gated content, newsletter), contact, demo (demo or sales request), quote, application, survey, checkout or other. First sample: demo.
goalyesWhat the person says about the form and their business, in their words, or none. First sample: B2B analytics tool. Sales qualifies leads by company size and country, and we never phone anyone before the demo.
fieldsyesOne line per field: q1. Label | kind, input | required or optional | how the label shows | about N s, then ticked already for a box that starts ticked and team uses it when the team says it uses the answer. First sample: q1. First name | first name, text input | required | label shown | about 3 s ...
factsyesWhat the page found: the counts, the skill's rule of thumb for this many fields, hidden fields, and every lint finding, one per line. First sample: 12 fields shown (9 required, 3 optional), about 74 s to fill in by a rough typing-time guess. ...
buttonyesThe submit button's text as it is now, or none found. First sample: Submit.
retry_notenoOnly on a reformat retry: why the previous reply was unusable.

How the page reads the form (FormKit.form): up to 300,000 characters. Text holding an input, select, textarea, form, button or label tag is read as HTML, anything else as a plain list. HTML is read as text and never run: inputs, dropdowns with their options, text boxes, radio groups and tick-box groups (each group folded into one question), the label tied to each field - a label element pointing at it or wrapping it, an aria-label, a fieldset legend or short text just before it, otherwise the placeholder or the code name - the required marks (the required attribute, aria-required or an asterisk in the label), hidden fields (counted, never planned), a CAPTCHA and the submit button. A plain list is one field per line, its parts split by | or tabs: the label first (an asterisk, or (required), marks it required; a type may sit in brackets after it, as in What's the problem? (textarea)*), then required or optional, a type (email, phone, dropdown, radio, checkbox, textarea, date, file ...) and options split by / or commas (or after options:); a line button: ... sets the button. With no required or optional mark anywhere in the list, every field is read as required. A button typed in the form's own box wins over the one found in the paste.

What the page works out, free and before any run. Kind: from the label, the code name, the autocomplete hint and the input type - email, email again, full, first or last name, phone, company, job title, company size, budget, website, the address parts, country, message, how they heard of you, consent box, date, number, file, password, a choice, several choices, a tick box or short text. How the label shows: label shown; placeholder only; screen-reader label only; code name only; no label. Seconds: a rough typing-time guess per kind (a dropdown or radio group about 3, more for one with over 15 options), halved when the field is optional, and summed for the form - never a measurement, and the page says so wherever it shows them. Findings (FormKit.lint), against the source skill's field-by-field rules: a placeholder-only or missing label, a repeated email, an email or phone field without the matching input type, no autocomplete hint, a split name, a required phone or free-text box, a dropdown of two to four options or of more than 15, a dropdown that opens on a real answer, a box ticked already, a password; and for the whole form: a sensitive field before the easy ones, everything required, more than six fields, a CAPTCHA, a weak or missing button. The rule of thumb (FormKit.band): three fields or fewer is the baseline, four to six about 10 to 25% fewer finish, seven or more about 25 to 50% or more fewer - a guide, sent as one of the facts. Type: the one you pick, or a guess from the fields (checked in this order: a file upload means an application; a budget with an address part a quote; a company, job title or company size a demo request; a message a contact form; a street address or postal code a quote; an email in three fields or fewer a lead form; anything else is other). Team uses it: a field whose key - its label in lower case, every run of other characters a space, as in what s the problem - is in uses is sent with team uses it, and the run may not drop it. The first 30 fields get the ids q1 ... q30, each sent as one line (FormKit.fieldLine); the rest are named in the facts and get no action (FormKit.factLines writes the counts, the rule of thumb, the hidden fields and every finding, one per line). The page asks for a pasted form with at least one field before it builds an input.

The model's rules

As the system prompt states them, in order:

  1. fields - one entry for each line in fields, in id order: field (its id), action, into, label, error and why (one sentence, at most 20 words, using only that line, kind, goal and facts). The actions:
    • keep - stays on the form and is required.
    • optional - stays on the form and is not required.
    • later - leaves this form; ask it after they submit (on the thank-you page, in the follow-up or on the call).
    • merge - folds into another field that stays, as one answer: only parts of a name (first, last, full) into each other, parts of a street address (street, city, state or region, postal code) into each other, or a repeated email into the email. into is the id of the field it folds into, which must be keep or optional. into is "" for every other action.
    • drop - removed.
  2. Never drop a field marked team uses it; it may be keep, optional, later or merge. Keep at least one way to reach them - an email or a phone field - as keep. Never add a field that is not in fields.
  3. label and error - for keep and optional: label is the visible label, at most 6 words, plain, with no asterisk and never starting with "Enter" or "Please"; error is the message shown when the answer is missing or wrong, at most 15 words, saying what to do, with no example values that contain digits. For later, merge and drop both are "". A field that takes in a merged one gets the label for the whole answer.
  4. order - the ids of every keep and optional field, each once: easy fields first (email, name), sensitive ones last (phone, company size, budget, address, files).
  5. steps - when more than six fields stay (keep plus optional), split order into 2 or 3 steps, in order's order, each { "title": at most 4 words, "fields": [ids] }, every staying field in exactly one step. Otherwise [].
  6. verdict - steps when more than six fields stay; trim when six or fewer stay and any field is later, merge or drop; lean when six or fewer stay and every field is keep or optional.
  7. button - the new button text: an action plus what they get, at most 5 words, never just "Submit" or "Send".
  8. trust - one line shown under the button that answers the likeliest worry for this kind of form, promising only what goal states; "" when goal states nothing to promise.
  9. tests - 2 or 3 A/B tests for this form, each { "change": what to try, "expect": the outcome you expect and why }, each at most 20 words.
  10. note - one sentence, at most 30 words: what changes and the first thing to do.

And the house rules:

The reply

One JSON object, in the shape the system prompt asks for:

{
  "task": "trim",
  "verdict": "lean | trim | steps",
  "fields": [ { "field": "q1", "action": "keep | optional | later | merge | drop", "into": "an id or empty", "label": "new label or empty", "error": "error message or empty", "why": "one sentence" } ],
  "order": [ "q1" ],
  "steps": [ { "title": "a short title", "fields": [ "q1" ] } ],
  "button": "new button text",
  "trust": "one line or empty",
  "tests": [ { "change": "what to try", "expect": "what you expect and why" } ],
  "note": "one sentence"
}

The page reads the reply with ReplyKit.normalize (the verdict, the field ids and the actions matched in upper or lower letters, multi-step or split read as steps; an entry with no field id is dropped) and keeps what it trusts with ReplyKit.trusted(reply, built): an entry is set aside, with the reason, when it names a field the page did not send, a field that already has an action, or an action the page does not know, or when it merges a field into itself, into a field the page did not send, into a field that does not stay, or across two different answers (only the parts of a name, the parts of a street address, or a repeated email into the email may merge); every listed field with no action is listed as missing and stays as it is. t.acts maps each id to its action and t.plan to {action, into, label, error, why}. The fields, their kinds, the required marks and the seconds always come from the page's own reading, never from the reply.

ReplyKit.check(reply, built) lists, under "The page's check": a missing verdict, or one the plan itself contradicts (ReplyKit.expectVerdict(t, built): steps when more than six fields stay - keep, optional or no action - trim when six or fewer stay and any field is later, merge or drop, lean otherwise); every entry set aside, and every listed field with no action; a field your team uses that the reply drops; a staying field with no label or no error message, or a label with an asterisk, starting with Enter or Please, or longer than six words; no email or phone field left required; an order that repeats a field, lists one that does not stay or leaves one out, or asks a sensitive field before the easy ones; steps for six or fewer staying fields, no steps for more, or steps that do not hold every staying field exactly once; no button, a button still as weak as a bare Submit or Send (FormKit.WEAK) or one longer than five words; fewer than two or more than three tests; and a number in any sentence, label, error, title or test that is not in the input, or a sentence that names a field id or retry_note or uses backticks. Any warning makes the status flagged; otherwise it is kept. The verdict on the page reads "Already lean" or "Trim it" or "Split into steps".

What the page builds from it: ReplyKit.design(built, t, reply) is the shorter form - the staying fields in the reply's order (or in its steps, when they hold), each with its new label, required only when keep, its error message, and a field that takes in a merged one answering the whole name or address; the fields past the list stay as they are, at the end - with the new button, the line under it, and its own count of fields, required fields and rough seconds. FormKit.formHtml(design) writes that form as plain HTML (a visible label on every field, optional ones marked, the matching input types and autocomplete hints, each error message tied to its field and hidden until needed, one fieldset per step, no script), which is what the page previews (FormView.preview) and exports. FormKit.toCSV(form, t.plan) writes one row per field, the ones past the list included - id, label, kind, input, required, how the label shows, seconds, findings, whether your team uses it, the plan, what it merges into, the new label, the error message and the reason - with CRLF line ends; ReplyKit.md and ReplyKit.json write the before and after counts, every field with its plan, the steps, the button, the tests and the findings. Before any run, FormView.freePlan(built) is the one fix the page makes alone: a repeated email is dropped when there is an email field.

Worked examples - every sample

These are the free samples on the page, one per kind of form: the form as pasted, how the page reads it - every field with its kind, how its label shows, its rough seconds, whether the team uses it and its findings - the exact input it sends, the reply that replays for free, and what the page does with it - the plan it trusts field by field, the verdict against the plan itself, the shorter form's own count, and its check. Together they cover the verdicts trim, lean and steps.

A demo request (HTML)

The form: type Demo or sales request; what they say about it: "B2B analytics tool. Sales qualifies leads by company size and country, and we never phone anyone before the demo."; the button box: left blank; marked "team uses it": work email, company, company size, country.

The form as pasted:

<form id="demo-request" action="https://forms.example.com/submit/8812" method="post">
  <h2>Book a demo</h2>
  <div class="row">
    <div class="col"><label for="fn">First name *</label><input id="fn" name="firstname" type="text" required></div>
    <div class="col"><label for="ln">Last name *</label><input id="ln" name="lastname" type="text" required></div>
  </div>
  <label for="em">Work email *</label>
  <input id="em" name="email" type="text" required>
  <label for="em2">Confirm work email *</label>
  <input id="em2" name="email_confirm" type="text" required>
  <input name="phone" type="text" placeholder="Phone number *" required>
  <label for="co">Company *</label>
  <input id="co" name="company" type="text" required>
  <label for="jt">Job title</label>
  <select id="jt" name="jobtitle">
    <option value="">Select...</option><option>C-level</option><option>VP</option><option>Director</option><option>Manager</option><option>Individual contributor</option><option>Other</option>
  </select>
  <label for="cs">Company size *</label>
  <select id="cs" name="company_size" required>
    <option>1-10</option><option>11-50</option><option>51-200</option><option>201-1000</option><option>1000+</option>
  </select>
  <label for="ct">Country *</label>
  <select id="ct" name="country" required>
    <option value="">Select country</option><option>United States</option><option>Canada</option><option>United Kingdom</option><option>Ireland</option><option>Germany</option><option>France</option><option>Spain</option><option>Italy</option><option>Netherlands</option><option>Belgium</option><option>Sweden</option><option>Norway</option><option>Denmark</option><option>Finland</option><option>Poland</option><option>Australia</option><option>New Zealand</option><option>Japan</option><option>Singapore</option><option>India</option><option>Brazil</option><option>Mexico</option>
  </select>
  <label for="src">How did you hear about us?</label>
  <select id="src" name="lead_source">
    <option value="">Select...</option><option>Search</option><option>LinkedIn</option><option>A colleague</option><option>An event</option>
  </select>
  <label for="msg">What would you like to see in the demo? *</label>
  <textarea id="msg" name="message" rows="5" required></textarea>
  <label><input type="checkbox" name="marketing_optin" checked> Yes, send me product updates and marketing emails</label>
  <input type="hidden" name="utm_source" value="">
  <input type="hidden" name="utm_campaign" value="">
  <div class="g-recaptcha" data-sitekey="6Lc-placeholder"></div>
  <button type="submit">Submit</button>
</form>

The page reads the paste (37 lines) as HTML, read without running it: 12 fields, 9 required and 3 optional, about 74 s to fill in by the rough typing-time guess, plus 2 hidden fields it counts but does not plan (utm_source, utm_campaign). The source skill's rule of thumb - 7 or more fields: about 25 to 50% or more fewer finish. The type: Demo or sales request. The button: "Submit" (found in the paste). 22 findings, 7 of them defects the source skill names outright. A CAPTCHA was found.

The fields (FormKit.form), in the order of the paste, each listed one sent as one line (FormKit.fieldLine):

IdFieldKind and inputRequiredLabel showsSecondsTeam uses itFindings
q1First namefirst name, text inputyeslabel shown3autocomplete, split_name
q2Last namelast name, text inputyeslabel shown3autocomplete
q3Work emailemail, text inputyeslabel shown6yesemail_type, autocomplete
q4Confirm work emailemail again (confirm), text inputyeslabel shown6confirm_email, email_type
q5Phone numberphone, text inputyesplaceholder only7placeholder_label, tel_type, autocomplete, phone_required
q6Companycompany, text inputyeslabel shown5yesautocomplete
q7Job titlejob title or role, dropdown of 6 options: C-level / VP / Director / Manager / Individual contributor / Othernolabel shown2autocomplete
q8Company sizecompany size, dropdown of 5 options: 1-10 / 11-50 / 51-200 / 201-1000 / 1000+yeslabel shown3yesno_prompt
q9Countrycountry, dropdown of 22 options: United States / Canada / United Kingdom / Ireland / Germany / France / Spain / Italy +14 moreyeslabel shown6yesautocomplete, many_options
q10How did you hear about us?how they heard of you, dropdown of 4 options: Search / LinkedIn / A colleague / An eventnolabel shown2few_options
q11What would you like to see in the demo?free-text message, text boxyeslabel shown30message_required
q12Yes, send me product updates and marketing emailsconsent box, tick boxnolabel shown1pre_ticked

Input:

{
 "task": "trim",
 "kind": "demo",
 "goal": "B2B analytics tool. Sales qualifies leads by company size and country, and we never phone anyone before the demo.",
 "fields": "q1. First name | first name, text input | required | label shown | about 3 s\nq2. Last name | last name, text input | required | label shown | about 3 s\nq3. Work email | email, text input | required | label shown | about 6 s | team uses it\nq4. Confirm work email | email again (confirm), text input | required | label shown | about 6 s\nq5. Phone number | phone, text input | required | placeholder only | about 7 s\nq6. Company | company, text input | required | label shown | about 5 s | team uses it\nq7. Job title | job title or role, dropdown of 6 options: C-level / VP / Director / Manager / Individual contributor / Other | optional | label shown | about 2 s\nq8. Company size | company size, dropdown of 5 options: 1-10 / 11-50 / 51-200 / 201-1000 / 1000+ | required | label shown | about 3 s | team uses it\nq9. Country | country, dropdown of 22 options: United States / Canada / United Kingdom / Ireland / Germany / France / Spain / Italy +14 more | required | label shown | about 6 s | team uses it\nq10. How did you hear about us? | how they heard of you, dropdown of 4 options: Search / LinkedIn / A colleague / An event | optional | label shown | about 2 s\nq11. What would you like to see in the demo? | free-text message, text box | required | label shown | about 30 s\nq12. Yes, send me product updates and marketing emails | consent box, tick box | optional | label shown | about 1 s | ticked already",
 "facts": "12 fields shown (9 required, 3 optional), about 74 s to fill in by a rough typing-time guess.\nThe source skill's rule of thumb - 7 or more fields: about 25 to 50% or more fewer finish.\n2 hidden fields not shown to people: utm_source, utm_campaign.\n\"Work email\" is not an email input, so phones do not show the email keyboard.\n\"Confirm work email\" asks for the email a second time; one email field is enough.\n\"Confirm work email\" is not an email input, so phones do not show the email keyboard.\n\"Phone number\" shows its label only as placeholder text, which disappears once they start typing.\n\"Phone number\" is not a phone input, so phones do not show the number pad.\n\"Phone number\" is required; the source skill says make the phone optional, or say why it is needed.\n\"Company size\" opens on a real answer (\"1-10\"), so a skipped question looks answered.\n\"Country\" is a plain dropdown of 22 options; make it searchable or shorter.\n\"How did you hear about us?\" is a dropdown of 4 options; radio buttons would show them at once.\n\"What would you like to see in the demo?\" is a required free-text box.\n\"Yes, send me product updates and marketing emails\" is ticked already; consent has to be given, not assumed.\nThe name is split into \"First name\" and \"Last name\"; one name field is less to fill.\n12 fields on one screen; past six the source skill suggests steps with a progress bar.\nThe button says \"Submit\", which does not say what they get.\nA CAPTCHA sits on the form - one more step before they can send it.\nNo autocomplete hint, so the browser cannot fill them in: First name, Last name, Work email, Phone number, Company, Job title, Country.",
 "button": "Submit"
}

Reply:

{
 "task": "trim",
 "verdict": "trim",
 "fields": [
  {
   "field": "q1",
   "action": "keep",
   "into": "",
   "label": "Full name",
   "error": "Add your full name to continue.",
   "why": "One name field is less to fill than two, so last name folds in here."
  },
  {
   "field": "q2",
   "action": "merge",
   "into": "q1",
   "label": "",
   "error": "",
   "why": "Folds into full name since one name field is less to fill than two separate ones."
  },
  {
   "field": "q3",
   "action": "keep",
   "into": "",
   "label": "Work email",
   "error": "Add a valid work email so we can reach you.",
   "why": "Work email is the one way to reach you, and the team uses it to follow up."
  },
  {
   "field": "q4",
   "action": "merge",
   "into": "q3",
   "label": "",
   "error": "",
   "why": "One email field is enough, so this folds into work email instead of asking twice."
  },
  {
   "field": "q5",
   "action": "later",
   "into": "",
   "label": "",
   "error": "",
   "why": "Since you never phone before the demo, this can wait until after they submit or the call."
  },
  {
   "field": "q6",
   "action": "keep",
   "into": "",
   "label": "Company name",
   "error": "Add your company name to continue.",
   "why": "Company name tells the team which account this is, and the team uses it already."
  },
  {
   "field": "q7",
   "action": "later",
   "into": "",
   "label": "",
   "error": "",
   "why": "Goal says leads are qualified by company size and country, not job title, so this can wait until after."
  },
  {
   "field": "q8",
   "action": "keep",
   "into": "",
   "label": "Company size",
   "error": "Choose your company size to continue.",
   "why": "Goal says the team qualifies leads by company size, and this field already uses it."
  },
  {
   "field": "q9",
   "action": "keep",
   "into": "",
   "label": "Country",
   "error": "Choose your country to continue.",
   "why": "Goal says the team qualifies leads by country, so this stays and the team already uses it."
  },
  {
   "field": "q10",
   "action": "later",
   "into": "",
   "label": "",
   "error": "",
   "why": "This does not help qualify a lead by company size or country, so it can wait until after."
  },
  {
   "field": "q11",
   "action": "optional",
   "into": "",
   "label": "What to see in demo",
   "error": "Add more detail, or leave this blank.",
   "why": "This can help the team prepare, but making it required adds about 30 seconds, so it's not required now."
  },
  {
   "field": "q12",
   "action": "later",
   "into": "",
   "label": "",
   "error": "",
   "why": "Consent must be given, not assumed, so this is better asked again after they submit."
  }
 ],
 "order": [
  "q1",
  "q3",
  "q6",
  "q9",
  "q8",
  "q11"
 ],
 "steps": [],
 "button": "Book my demo",
 "trust": "We won't call you before your demo.",
 "tests": [
  {
   "change": "Combine first and last name into one field.",
   "expect": "Fewer fields should raise completions, matching the rule of thumb for 7 or more fields."
  },
  {
   "change": "Make the demo question no longer required.",
   "expect": "More people may finish since that question alone takes about 30 seconds to answer."
  },
  {
   "change": "Ask how they heard about the business after they submit the form.",
   "expect": "Removing a low-value question from the form should help completions too, per the same rule of thumb."
  }
 ],
 "note": "This cuts the form from 12 fields to six by joining the name fields and the repeated email, then moving the rest after submission; start with the name fields."
}

The plan the page trusts (ReplyKit.trusted), field by field:

FieldActionNew labelError messageWhy
First name (q1)keep (keep, required)Full nameAdd your full name to continue.One name field is less to fill than two, so last name folds in here.
Last name (q2)merge into First name--Folds into full name since one name field is less to fill than two separate ones.
Work email (q3)keep (keep, required)Work emailAdd a valid work email so we can reach you.Work email is the one way to reach you, and the team uses it to follow up.
Confirm work email (q4)merge into Work email--One email field is enough, so this folds into work email instead of asking twice.
Phone number (q5)later (ask after they submit)--Since you never phone before the demo, this can wait until after they submit or the call.
Company (q6)keep (keep, required)Company nameAdd your company name to continue.Company name tells the team which account this is, and the team uses it already.
Job title (q7)later (ask after they submit)--Goal says leads are qualified by company size and country, not job title, so this can wait until after.
Company size (q8)keep (keep, required)Company sizeChoose your company size to continue.Goal says the team qualifies leads by company size, and this field already uses it.
Country (q9)keep (keep, required)CountryChoose your country to continue.Goal says the team qualifies leads by country, so this stays and the team already uses it.
How did you hear about us? (q10)later (ask after they submit)--This does not help qualify a lead by company size or country, so it can wait until after.
What would you like to see in the demo? (q11)optional (keep, optional)What to see in demoAdd more detail, or leave this blank.This can help the team prepare, but making it required adds about 30 seconds, so it's not required now.
Yes, send me product updates and marketing emails (q12)later (ask after they submit)--Consent must be given, not assumed, so this is better asked again after they submit.

The order: First name, Work email, Company, Country, Company size, What would you like to see in the demo?.

The new button: "Book my demo"; under it: "We won't call you before your demo."

The tests:

The shorter form (ReplyKit.design): 6 fields, 5 required, about 39 s by the same rough guess (from 12 fields and about 74 s).

The verdict: trim - "Trim it"; it agrees with the plan, which leaves 6 fields.

The note: "This cuts the form from 12 fields to six by joining the name fields and the repeated email, then moving the rest after submission; start with the name fields."

The page's check (ReplyKit.check): kept.

No notes.

A plumber's contact form (list)

The form: type Contact form; what they say about it: "Emergency plumber. We ring back within the hour, so the phone number matters more than the email."; the button box: left blank; marked "team uses it": phone, postcode, what s the problem.

The form as pasted:

Full name*
Email*
Phone*
Street address*
Postcode*
What's the problem? (textarea)*
Preferred visit time | dropdown | optional | Morning / Afternoon / Evening
How did you hear about us? | optional | options: Google, A neighbour, Our van, A leaflet
button: Send

The page reads the paste (9 lines) as a plain list, one field per line: 8 fields, 6 required and 2 optional, about 65 s to fill in by the rough typing-time guess. The source skill's rule of thumb - 7 or more fields: about 25 to 50% or more fewer finish. The type: Contact form. The button: "Send" (found in the paste). 6 findings, 1 of them defects the source skill names outright.

The fields (FormKit.form), in the order of the paste, each listed one sent as one line (FormKit.fieldLine):

IdFieldKind and inputRequiredLabel showsSecondsTeam uses itFindings
q1Full namefull nameyeslabel shown4none
q2Emailemailyeslabel shown6none
q3Phonephoneyeslabel shown7yesphone_required
q4Street addressstreet addressyeslabel shown10none
q5Postcodepostal codeyeslabel shown4yesnone
q6What's the problem?free-text message, text boxyeslabel shown30yesmessage_required
q7Preferred visit timeone choice, dropdown of 3 options: Morning / Afternoon / Eveningnolabel shown2few_options
q8How did you hear about us?how they heard of you, dropdown of 4 options: Google / A neighbour / Our van / A leafletnolabel shown2few_options

Input:

{
 "task": "trim",
 "kind": "contact",
 "goal": "Emergency plumber. We ring back within the hour, so the phone number matters more than the email.",
 "fields": "q1. Full name | full name | required | label shown | about 4 s\nq2. Email | email | required | label shown | about 6 s\nq3. Phone | phone | required | label shown | about 7 s | team uses it\nq4. Street address | street address | required | label shown | about 10 s\nq5. Postcode | postal code | required | label shown | about 4 s | team uses it\nq6. What's the problem? | free-text message, text box | required | label shown | about 30 s | team uses it\nq7. Preferred visit time | one choice, dropdown of 3 options: Morning / Afternoon / Evening | optional | label shown | about 2 s\nq8. How did you hear about us? | how they heard of you, dropdown of 4 options: Google / A neighbour / Our van / A leaflet | optional | label shown | about 2 s",
 "facts": "8 fields shown (6 required, 2 optional), about 65 s to fill in by a rough typing-time guess.\nThe source skill's rule of thumb - 7 or more fields: about 25 to 50% or more fewer finish.\n\"Phone\" is required; the source skill says make the phone optional, or say why it is needed.\n\"What's the problem?\" is a required free-text box.\n\"Preferred visit time\" is a dropdown of 3 options; radio buttons would show them at once.\n\"How did you hear about us?\" is a dropdown of 4 options; radio buttons would show them at once.\n8 fields on one screen; past six the source skill suggests steps with a progress bar.\nThe button says \"Send\", which does not say what they get.",
 "button": "Send"
}

Reply:

{
 "task": "trim",
 "verdict": "trim",
 "fields": [
  {
   "field": "q1",
   "action": "keep",
   "into": "",
   "label": "Your name",
   "error": "Tell us your name so we can help.",
   "why": "Your name takes about 4 s and lets the team address the caller by name."
  },
  {
   "field": "q2",
   "action": "optional",
   "into": "",
   "label": "Email address",
   "error": "Check your email address and try again.",
   "why": "Email address takes about 6 s, but phone matters more for a call back, so email can be optional."
  },
  {
   "field": "q3",
   "action": "keep",
   "into": "",
   "label": "Phone number",
   "error": "Add a phone number so we can call you back.",
   "why": "Phone number is used by the team and the goal says it matters more than email for the call back."
  },
  {
   "field": "q4",
   "action": "later",
   "into": "",
   "label": "",
   "error": "",
   "why": "Street address takes about 10 s and can be collected on the call back instead of before it."
  },
  {
   "field": "q5",
   "action": "keep",
   "into": "",
   "label": "Postcode",
   "error": "Enter your postcode so we can find you.",
   "why": "Postcode takes about 4 s to answer and the team already uses the answer, so it stays required."
  },
  {
   "field": "q6",
   "action": "keep",
   "into": "",
   "label": "Describe the problem",
   "error": "Tell us what's wrong so we can help fix it.",
   "why": "Describing the problem is used by the team and lets them know what needs fixing on the call back."
  },
  {
   "field": "q7",
   "action": "optional",
   "into": "",
   "label": "Preferred visit time",
   "error": "Choose a time that works for you.",
   "why": "Preferred visit time takes about 2 s and is optional since it is helpful but not required to start helping."
  },
  {
   "field": "q8",
   "action": "later",
   "into": "",
   "label": "",
   "error": "",
   "why": "Hearing how someone found the service takes about 2 s but is not needed before the team can help them."
  }
 ],
 "order": [
  "q2",
  "q1",
  "q6",
  "q7",
  "q3",
  "q5"
 ],
 "steps": [],
 "button": "Get your call back",
 "trust": "We call back within the hour.",
 "tests": [
  {
   "change": "Make Email address optional while keeping Phone number required.",
   "expect": "More people finish since the goal says phone matters more than email for a call back."
  },
  {
   "change": "Move street address and how they heard about us off this form, asking them later.",
   "expect": "Fewer required fields, 6 instead of 8, should raise completion, per the skill's rule of thumb."
  },
  {
   "change": "Replace the old button text with Get your call back.",
   "expect": "Clicks should rise since the new button says what they get, unlike the old one."
  }
 ],
 "note": "This cuts the form to 6 fields by moving street address and how they heard about us to follow up, then start by publishing the shorter form and watching completion."
}

The plan the page trusts (ReplyKit.trusted), field by field:

FieldActionNew labelError messageWhy
Full name (q1)keep (keep, required)Your nameTell us your name so we can help.Your name takes about 4 s and lets the team address the caller by name.
Email (q2)optional (keep, optional)Email addressCheck your email address and try again.Email address takes about 6 s, but phone matters more for a call back, so email can be optional.
Phone (q3)keep (keep, required)Phone numberAdd a phone number so we can call you back.Phone number is used by the team and the goal says it matters more than email for the call back.
Street address (q4)later (ask after they submit)--Street address takes about 10 s and can be collected on the call back instead of before it.
Postcode (q5)keep (keep, required)PostcodeEnter your postcode so we can find you.Postcode takes about 4 s to answer and the team already uses the answer, so it stays required.
What's the problem? (q6)keep (keep, required)Describe the problemTell us what's wrong so we can help fix it.Describing the problem is used by the team and lets them know what needs fixing on the call back.
Preferred visit time (q7)optional (keep, optional)Preferred visit timeChoose a time that works for you.Preferred visit time takes about 2 s and is optional since it is helpful but not required to start helping.
How did you hear about us? (q8)later (ask after they submit)--Hearing how someone found the service takes about 2 s but is not needed before the team can help them.

The order: Email, Full name, What's the problem?, Preferred visit time, Phone, Postcode.

The new button: "Get your call back"; under it: "We call back within the hour."

The tests:

The shorter form (ReplyKit.design): 6 fields, 4 required, about 50 s by the same rough guess (from 8 fields and about 65 s).

The verdict: trim - "Trim it"; it agrees with the plan, which leaves 6 fields.

The note: "This cuts the form to 6 fields by moving street address and how they heard about us to follow up, then start by publishing the shorter form and watching completion."

The page's check (ReplyKit.check): kept.

No notes.

A gated guide (HTML)

The form: type Lead capture (gated content, newsletter); what they say about it: "Gated salary guide; the email goes to our newsletter list."; the button box: left blank; marked "team uses it": work email.

The form as pasted:

<form action="/subscribe" method="post" class="guide-form">
  <h3>Get the 2026 salary guide</h3>
  <p>Pay bands for 40 roles across 12 cities, in one PDF.</p>
  <label for="gemail">Work email</label>
  <input id="gemail" name="email" type="email" autocomplete="email" required placeholder="name@company.com">
  <button type="submit">Email me the guide</button>
  <p class="small">No spam - unsubscribe anytime.</p>
</form>

The page reads the paste (8 lines) as HTML, read without running it: 1 field, 1 required and 0 optional, about 6 s to fill in by the rough typing-time guess. The source skill's rule of thumb - 3 fields or fewer: the baseline. The type: Lead capture (gated content, newsletter). The button: "Email me the guide" (found in the paste). 0 findings, 0 of them defects the source skill names outright.

The fields (FormKit.form), in the order of the paste, each listed one sent as one line (FormKit.fieldLine):

IdFieldKind and inputRequiredLabel showsSecondsTeam uses itFindings
q1Work emailemail, email inputyeslabel shown6yesnone

Input:

{
 "task": "trim",
 "kind": "lead",
 "goal": "Gated salary guide; the email goes to our newsletter list.",
 "fields": "q1. Work email | email, email input | required | label shown | about 6 s | team uses it",
 "facts": "1 field shown (1 required, 0 optional), about 6 s to fill in by a rough typing-time guess.\nThe source skill's rule of thumb - 3 fields or fewer: the baseline.",
 "button": "Email me the guide"
}

Reply:

{
 "task": "trim",
 "verdict": "lean",
 "fields": [
  {
   "field": "q1",
   "action": "keep",
   "into": "",
   "label": "Work email",
   "error": "Enter a valid work email so we can send the guide.",
   "why": "Work email is the only field, already used by the team, and needed to send the gated guide."
  }
 ],
 "order": [
  "q1"
 ],
 "steps": [],
 "button": "Get the salary guide",
 "trust": "Your email is only added to our newsletter when you get the guide.",
 "tests": [
  {
   "change": "Show the trust line next to the email field instead of only beneath the button.",
   "expect": "May raise completions since the entire form is about 6 seconds and already has just 1 required field."
  },
  {
   "change": "Test the button text against the current wording to see which gets more submissions.",
   "expect": "A direct benefit phrase could outperform a generic one, since the goal names the guide as the benefit."
  }
 ],
 "note": "The form already asks only the work email needed to send the guide, so it stays required; first, update the button and add the trust line about the newsletter."
}

The plan the page trusts (ReplyKit.trusted), field by field:

FieldActionNew labelError messageWhy
Work email (q1)keep (keep, required)Work emailEnter a valid work email so we can send the guide.Work email is the only field, already used by the team, and needed to send the gated guide.

The order: Work email.

The new button: "Get the salary guide"; under it: "Your email is only added to our newsletter when you get the guide."

The tests:

The shorter form (ReplyKit.design): 1 field, 1 required, about 6 s by the same rough guess (from 1 field and about 6 s).

The verdict: lean - "Already lean"; it agrees with the plan, which leaves 1 field.

The note: "The form already asks only the work email needed to send the guide, so it stays required; first, update the button and add the trust line about the newsletter."

The page's check (ReplyKit.check): kept.

No notes.

A moving quote (HTML)

The form: type Quote or estimate request; what they say about it: "Removals firm. We send a fixed price by email the same day, and we cannot price a move without both postcodes, the date, the home size, the access and the extras."; the button box: left blank; marked "team uses it": email, moving from postcode, moving to postcode, moving date, size of home, stairs or a lift at either end, extras, anything heavy or fragile piano safe artwork.

The form as pasted:

<form class="quote" action="/quote" method="post">
  <h2>Get a moving quote</h2>
  <label for="q-name">Your name</label><input id="q-name" name="name" autocomplete="name" required>
  <label for="q-email">Email</label><input id="q-email" name="email" type="email" autocomplete="email" required>
  <label for="q-phone">Phone</label><input id="q-phone" name="phone" type="tel" autocomplete="tel" required>
  <label for="q-from">Moving from (postcode)</label><input id="q-from" name="from_postcode" required>
  <label for="q-to">Moving to (postcode)</label><input id="q-to" name="to_postcode" required>
  <label for="q-date">Moving date</label><input id="q-date" name="move_date" type="date" required>
  <label for="q-size">Size of home</label>
  <select id="q-size" name="home_size" required>
    <option value="">Choose one</option><option>Studio</option><option>1 bedroom</option><option>2 bedrooms</option><option>3 bedrooms</option><option>4+ bedrooms</option>
  </select>
  <fieldset><legend>Stairs or a lift at either end?</legend>
    <label><input type="radio" name="access" value="ground" required> Ground floor only</label>
    <label><input type="radio" name="access" value="lift"> Lift</label>
    <label><input type="radio" name="access" value="stairs"> Stairs</label>
  </fieldset>
  <fieldset><legend>Extras</legend>
    <label><input type="checkbox" name="extras" value="packing"> Packing</label>
    <label><input type="checkbox" name="extras" value="storage"> Storage</label>
    <label><input type="checkbox" name="extras" value="dismantling"> Furniture dismantling</label>
  </fieldset>
  <label for="q-items">Anything heavy or fragile? (piano, safe, artwork)</label><textarea id="q-items" name="special_items" required></textarea>
  <label for="q-src">How did you hear about us?</label><input id="q-src" name="source">
  <button type="submit">Submit</button>
</form>

The page reads the paste (26 lines) as HTML, read without running it: 11 fields, 9 required and 2 optional, about 71 s to fill in by the rough typing-time guess. The source skill's rule of thumb - 7 or more fields: about 25 to 50% or more fewer finish. The type: Quote or estimate request. The button: "Submit" (found in the paste). 6 findings, 1 of them defects the source skill names outright.

The fields (FormKit.form), in the order of the paste, each listed one sent as one line (FormKit.fieldLine):

IdFieldKind and inputRequiredLabel showsSecondsTeam uses itFindings
q1Your namefull name, text inputyeslabel shown4none
q2Emailemail, email inputyeslabel shown6yesnone
q3Phonephone, tel inputyeslabel shown7phone_required
q4Moving from (postcode)postal code, text inputyeslabel shown4yesautocomplete
q5Moving to (postcode)postal code, text inputyeslabel shown4yesautocomplete
q6Moving datedate, date inputyeslabel shown6yesnone
q7Size of homeone choice, dropdown of 5 options: Studio / 1 bedroom / 2 bedrooms / 3 bedrooms / 4+ bedroomsyeslabel shown3yesnone
q8Stairs or a lift at either end?one choice, 3 radio options: Ground floor only / Lift / Stairsyeslabel shown3yesnone
q9Extrasseveral choices, 3 tick boxes: Packing / Storage / Furniture dismantlingnolabel shown2yesnone
q10Anything heavy or fragile? (piano, safe, artwork)free-text message, text boxyeslabel shown30yesmessage_required
q11How did you hear about us?how they heard of you, text inputnolabel shown2none

Input:

{
 "task": "trim",
 "kind": "quote",
 "goal": "Removals firm. We send a fixed price by email the same day, and we cannot price a move without both postcodes, the date, the home size, the access and the extras.",
 "fields": "q1. Your name | full name, text input | required | label shown | about 4 s\nq2. Email | email, email input | required | label shown | about 6 s | team uses it\nq3. Phone | phone, tel input | required | label shown | about 7 s\nq4. Moving from (postcode) | postal code, text input | required | label shown | about 4 s | team uses it\nq5. Moving to (postcode) | postal code, text input | required | label shown | about 4 s | team uses it\nq6. Moving date | date, date input | required | label shown | about 6 s | team uses it\nq7. Size of home | one choice, dropdown of 5 options: Studio / 1 bedroom / 2 bedrooms / 3 bedrooms / 4+ bedrooms | required | label shown | about 3 s | team uses it\nq8. Stairs or a lift at either end? | one choice, 3 radio options: Ground floor only / Lift / Stairs | required | label shown | about 3 s | team uses it\nq9. Extras | several choices, 3 tick boxes: Packing / Storage / Furniture dismantling | optional | label shown | about 2 s | team uses it\nq10. Anything heavy or fragile? (piano, safe, artwork) | free-text message, text box | required | label shown | about 30 s | team uses it\nq11. How did you hear about us? | how they heard of you, text input | optional | label shown | about 2 s",
 "facts": "11 fields shown (9 required, 2 optional), about 71 s to fill in by a rough typing-time guess.\nThe source skill's rule of thumb - 7 or more fields: about 25 to 50% or more fewer finish.\n\"Phone\" is required; the source skill says make the phone optional, or say why it is needed.\n\"Anything heavy or fragile? (piano, safe, artwork)\" is a required free-text box.\n11 fields on one screen; past six the source skill suggests steps with a progress bar.\nThe button says \"Submit\", which does not say what they get.\nNo autocomplete hint, so the browser cannot fill them in: Moving from (postcode), Moving to (postcode).",
 "button": "Submit"
}

Reply:

{
 "task": "trim",
 "verdict": "steps",
 "fields": [
  {
   "field": "q1",
   "action": "keep",
   "into": "",
   "label": "Your name",
   "error": "Add your name so we can address your quote.",
   "why": "Takes about 4 seconds and lets us address your fixed price email by name."
  },
  {
   "field": "q2",
   "action": "keep",
   "into": "",
   "label": "Email address",
   "error": "Enter a working email address so we can send your fixed price.",
   "why": "The team uses this, and the fixed price is sent by email the same day, so it stays required."
  },
  {
   "field": "q3",
   "action": "optional",
   "into": "",
   "label": "Phone number",
   "error": "Use numbers only so we can reach you if needed.",
   "why": "It is required with no stated reason, and the promise is only email, so it need not stay required."
  },
  {
   "field": "q4",
   "action": "keep",
   "into": "",
   "label": "Moving from postcode",
   "error": "Enter the postcode you are moving from.",
   "why": "Both postcodes are needed before a price can be given, and the team uses this answer."
  },
  {
   "field": "q5",
   "action": "keep",
   "into": "",
   "label": "Moving to postcode",
   "error": "Enter the postcode you are moving to.",
   "why": "The destination postcode is needed before a price can be given, and the team uses this answer."
  },
  {
   "field": "q6",
   "action": "keep",
   "into": "",
   "label": "Moving date",
   "error": "Enter the date you plan to move.",
   "why": "The move date is needed before a price can be given, and the team uses this answer."
  },
  {
   "field": "q7",
   "action": "keep",
   "into": "",
   "label": "Home size",
   "error": "Choose the option that matches your home size.",
   "why": "Home size is needed before a price can be given, and the team uses this answer."
  },
  {
   "field": "q8",
   "action": "keep",
   "into": "",
   "label": "Stairs or lift access",
   "error": "Choose the access option that matches both ends.",
   "why": "Access at each end changes the price, and the team uses this answer."
  },
  {
   "field": "q9",
   "action": "keep",
   "into": "",
   "label": "Extra services",
   "error": "Choose any extras you need, like packing or storage.",
   "why": "Knowing the extras is needed to price the move, and the team already uses this answer."
  },
  {
   "field": "q10",
   "action": "later",
   "into": "",
   "label": "",
   "error": "",
   "why": "This needs about 30 seconds and does not affect the price, so it can wait until after they submit."
  },
  {
   "field": "q11",
   "action": "later",
   "into": "",
   "label": "",
   "error": "",
   "why": "This does not help price the move and is not required now, so it can be asked after they submit."
  }
 ],
 "order": [
  "q2",
  "q1",
  "q4",
  "q5",
  "q6",
  "q7",
  "q8",
  "q9",
  "q3"
 ],
 "steps": [
  {
   "title": "About you",
   "fields": [
    "q2",
    "q1"
   ]
  },
  {
   "title": "Your move details",
   "fields": [
    "q4",
    "q5",
    "q6",
    "q7",
    "q8"
   ]
  },
  {
   "title": "Extras and phone",
   "fields": [
    "q9",
    "q3"
   ]
  }
 ],
 "button": "Get my fixed price",
 "trust": "You will receive your fixed price by email the same day.",
 "tests": [
  {
   "change": "Break the eleven-field form into a few short steps.",
   "expect": "Fewer people abandon it; a rough rule of thumb says forms this size can lose 25 to 50% of visitors."
  },
  {
   "change": "Stop requiring the phone number on the quote form.",
   "expect": "More people complete it, since a required phone number is already flagged as a reason people leave."
  },
  {
   "change": "Change the button to promise a fixed price by email.",
   "expect": "More people submit it, since they can see exactly what they get for finishing the form."
  }
 ],
 "note": "This turns the long one-page quote form into three short steps, no longer requires the phone number, and moves two fields to the follow-up; start by splitting the steps."
}

The plan the page trusts (ReplyKit.trusted), field by field:

FieldActionNew labelError messageWhy
Your name (q1)keep (keep, required)Your nameAdd your name so we can address your quote.Takes about 4 seconds and lets us address your fixed price email by name.
Email (q2)keep (keep, required)Email addressEnter a working email address so we can send your fixed price.The team uses this, and the fixed price is sent by email the same day, so it stays required.
Phone (q3)optional (keep, optional)Phone numberUse numbers only so we can reach you if needed.It is required with no stated reason, and the promise is only email, so it need not stay required.
Moving from (postcode) (q4)keep (keep, required)Moving from postcodeEnter the postcode you are moving from.Both postcodes are needed before a price can be given, and the team uses this answer.
Moving to (postcode) (q5)keep (keep, required)Moving to postcodeEnter the postcode you are moving to.The destination postcode is needed before a price can be given, and the team uses this answer.
Moving date (q6)keep (keep, required)Moving dateEnter the date you plan to move.The move date is needed before a price can be given, and the team uses this answer.
Size of home (q7)keep (keep, required)Home sizeChoose the option that matches your home size.Home size is needed before a price can be given, and the team uses this answer.
Stairs or a lift at either end? (q8)keep (keep, required)Stairs or lift accessChoose the access option that matches both ends.Access at each end changes the price, and the team uses this answer.
Extras (q9)keep (keep, required)Extra servicesChoose any extras you need, like packing or storage.Knowing the extras is needed to price the move, and the team already uses this answer.
Anything heavy or fragile? (piano, safe, artwork) (q10)later (ask after they submit)--This needs about 30 seconds and does not affect the price, so it can wait until after they submit.
How did you hear about us? (q11)later (ask after they submit)--This does not help price the move and is not required now, so it can be asked after they submit.

The steps: 1. About you - Email address, Your name; 2. Your move details - Moving from postcode, Moving to postcode, Moving date, Home size, Stairs or lift access; 3. Extras and phone - Extra services, Phone number.

The new button: "Get my fixed price"; under it: "You will receive your fixed price by email the same day."

The tests:

The shorter form (ReplyKit.design): 9 fields, 8 required, about 38 s by the same rough guess (from 11 fields and about 71 s).

The verdict: steps - "Split into steps"; it agrees with the plan, which leaves 9 fields.

The note: "This turns the long one-page quote form into three short steps, no longer requires the phone number, and moves two fields to the follow-up; start by splitting the steps."

The page's check (ReplyKit.check): kept.

No notes.

The CSV for the first sample

What FormKit.toCSV(form, t.plan) writes for "A demo request (HTML)" above: one row per field in the order of the paste, with the trusted plan (lines end in CRLF). A field past the list has no id and no plan.

field_id,label,kind,input,required,label_shows,seconds,findings,team_uses,plan,merge_into,new_label,error_message,why
q1,First name,first_name,text input,yes,label shown,3,autocomplete split_name,,keep,,Full name,Add your full name to continue.,"One name field is less to fill than two, so last name folds in here."
q2,Last name,last_name,text input,yes,label shown,3,autocomplete,,merge,q1,,,Folds into full name since one name field is less to fill than two separate ones.
q3,Work email,email,text input,yes,label shown,6,email_type autocomplete,yes,keep,,Work email,Add a valid work email so we can reach you.,"Work email is the one way to reach you, and the team uses it to follow up."
q4,Confirm work email,email_confirm,text input,yes,label shown,6,confirm_email email_type,,merge,q3,,,"One email field is enough, so this folds into work email instead of asking twice."
q5,Phone number,phone,text input,yes,placeholder only,7,placeholder_label tel_type autocomplete phone_required,,later,,,,"Since you never phone before the demo, this can wait until after they submit or the call."
q6,Company,company,text input,yes,label shown,5,autocomplete,yes,keep,,Company name,Add your company name to continue.,"Company name tells the team which account this is, and the team uses it already."
q7,Job title,job_title,dropdown of 6 options: C-level / VP / Director / Manager / Individual contributor / Other,no,label shown,2,autocomplete,,later,,,,"Goal says leads are qualified by company size and country, not job title, so this can wait until after."
q8,Company size,size,dropdown of 5 options: 1-10 / 11-50 / 51-200 / 201-1000 / 1000+,yes,label shown,3,no_prompt,yes,keep,,Company size,Choose your company size to continue.,"Goal says the team qualifies leads by company size, and this field already uses it."
q9,Country,country,dropdown of 22 options: United States / Canada / United Kingdom / Ireland / Germany / France / Spain / Italy +14 more,yes,label shown,6,autocomplete many_options,yes,keep,,Country,Choose your country to continue.,"Goal says the team qualifies leads by country, so this stays and the team already uses it."
q10,How did you hear about us?,source,dropdown of 4 options: Search / LinkedIn / A colleague / An event,no,label shown,2,few_options,,later,,,,"This does not help qualify a lead by company size or country, so it can wait until after."
q11,What would you like to see in the demo?,message,text box,yes,label shown,30,message_required,,optional,,What to see in demo,"Add more detail, or leave this blank.","This can help the team prepare, but making it required adds about 30 seconds, so it's not required now."
q12,"Yes, send me product updates and marketing emails",consent,tick box,no,label shown,1,pre_ticked,,later,,,,"Consent must be given, not assumed, so this is better asked again after they submit."

Build the input yourself

Load the engine in node (global.window = global; require("./formkit.js"); require("./replykit.js"); require("./formview.js")), then:

const K = FormKit, R = ReplyKit;
const form = K.form({ form: "Full name | required\nEmail | email | required\nPhone | phone | optional",
  type: "contact", goal: "Bakery; we answer every order question by email the same day.",
  button: "Send", uses: "email" });
// form.fields: every field in the order of the paste [{id, label, kind, req, src, secs, uses, finds, options}]
// form.items: the first 30 (q1 ...); form.rest: the ones past the list; form.findings: [{code, id, level, text}]
// form.total: rough seconds for the whole form; form.band: the rule of thumb; form.type: picked or guessed
const built = K.buildInput(form);     // built.ok is false, with built.note, until the paste has a field
// built.items: the listed fields; built.warn: what the page warns about
FormView.freePlan(built)              // the page's own fix, no run needed: a repeated email dropped
const wire = K.toWire(built.input);   // POST this to /estimate and /run
// then: const reply = R.normalize(R.parseReply(job.output.output));
// const t = R.trusted(reply, built)       // {acts: {q1: action}, plan: {q1: {action, into, label, error, why}}, dropped, missing}
// R.expectVerdict(t, built)                // the verdict the plan itself implies
// R.check(reply, built)                    // {status: kept or flagged, notes}
// K.toCSV(form, t.plan)                    // every field with its kind, seconds, findings and plan
// K.formHtml(R.design(built, t, reply))    // the shorter form as plain HTML

Form keys: form (the pasted HTML or plain list, any shape above), type (one of lead, contact, demo, quote, application, survey, checkout or other, or empty to let the page guess), goal (what you say about the form and your business, or empty), button (the button text, or empty to use the one found in the paste) and uses (the keys of the fields your team uses, one per line - each key is the field's label in lower case with every run of other characters a space, and a second field with the same key gets " #2" added). The engine sends nothing anywhere: it only reads the text you give it.

Step 1 — a tiny client helper

One function that sends JSON and unwraps the response. Paste a personal token from /tokens.html, or leave it empty and mint a guest token in step 2.

BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="paste-your-token-here"   # from https://fewer-fields.skillsafe.ai/tokens.html
api() { # api METHOD PATH [JSON-BODY]
  curl -s -X "$1" "$BASE$2" -H "Content-Type: application/json" \
    ${TOKEN:+-H "Authorization: Bearer $TOKEN"} ${3:+--data "$3"}
}

Step 2 — a token

A guest token is enough for /me and /estimate. A run is metered, so it needs a personal token: sign in on /tokens.html and copy it from there.

# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN=$(curl -s -X POST "$BASE/guest" -H "Content-Type: application/json" -d '{"slug":"fewer-fields"}' | jq -r '.data.token')

Step 3 — GET /me

Who the token is (subject_type: user or guest) and the credit balance.

api GET /me | jq '.data | {subject_type, credits}' 

Step 4 — POST /estimate

Free. The body is the run input itself, the bare object (never wrapped in {"input": ...}). hold_credits is a reservation, not the price; min_credits is what the balance must cover; model_alias reads gpt-terra.

# input.json: the run input object - {"task": "trim", "kind": "contact", "goal": "Bakery; we answer every order question by email the same day.", "fields": "q1. Full name | full name | required | label shown | about 4 s\nq2. Email | email, email input | required | label shown | about 6 s | team uses it\nq3. Phone | phone, tel input | optional | label shown | about 4 s", "facts": "3 fields shown (2 required, 1 optional), about 14 s to fill in by a rough typing-time guess.\nThe source skill's rule of thumb - 3 fields or fewer: the baseline.\nThe button says \"Send\", which does not say what they get.", "button": "Send"}
# - see the field table
api POST /estimate "$(cat input.json)" | jq '.data | {hold_credits, min_credits, model, model_alias, markup_bps, warnings}' 

Step 5 — POST /run, then poll the job

Send an Idempotency-Key built from the lane and the input: a retry with the same key never bills twice. The page uses fewer-fields:<task>:<inputHash>:a<attempt> (the one task is trim), and its one reformat retry (with retry_note added) is attempt 2, a new key and a second run. The job's output.output is the reply text; truncated: true means the balance cut it short.

KEY="fewer-fields:trim:$(shasum -a 256 input.json | cut -c1-16):a1"
JOB=$(curl -s -X POST "$BASE/run" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $KEY" --data @input.json | jq -r '.data.job_id')
while :; do
  J=$(api GET "/jobs/$JOB"); S=$(echo "$J" | jq -r '.data.status')
  case "$S" in succeeded|failed|cancelled) break;; esac; sleep 2
done
echo "$J" | jq -r '.data.output.output' > reply.json   # one JSON object, the reply contract
echo "$J" | jq '.data | {charged_credits, truncated}'
jq -r '.verdict, .note, (.fields[] | "\(.field) \(.action) \(.label)")' reply.json

Step 6 — POST /run-stream

The same run as Server-Sent Events, with the same input.json. Browsers receive progress ticks rather than text deltas; the final done event carries the reply.

# Server-Sent Events: tick/delta events while the job runs, then a final event with the job
curl -N -s -X POST "$BASE/run-stream" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: fewer-fields:trim:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json