Create a form: one open link that anyone can fill, for a post, a newsletter, a website, or a QR code. There is no list of people and there are no reminders. To chase a list of known people, use create_request. Returns a form_id, a preview_url, a test_link, and the open_link. Show the preview_url to the user.
FORM JSON. Minimal: {"title":"...","blocks":[{"id":"full_name","type":"short_text","label":"Full name","required":true}]}.
Use "pages":[{"id","title","blocks":[...],"next":[...]}] for steps and paths.
STYLE: set theme.preset. "like Tally"/simple/document -> "paper". "like Typeform"/one question at a time/conversational -> "focus".
Others: midnight (dark), sunrise (warm, conversational), forest (green, calm), mono (brutalist), lavender (playful, conversational, centered), ocean (corporate blue).
Override: theme.accent/background/text/... (hex), font (system|inter|grotesk|serif|rounded|mono), radius (none|small|medium|large|full), align (left|center), logo/cover (https URLs). "layout":"classic"|"conversational" forces the layout.
FIELD TYPES (all take id [a-z0-9_], label, description, required, showIf):
short_text, long_text (maxLength), email, phone, url, number (min,max,unit),
choice (options:["A","B"] or [{id,label,image}], multiple, maxSelect, allowOther, display:auto|list|dropdown|cards),
yes_no, rating (max 3-10, icon star|heart|thumb|bolt), scale (min 0|1, max<=10, minLabel, maxLabel; NPS = 0-10),
ranking (options), matrix (rows, columns), date (min,max YYYY-MM-DD), time,
file (multiple, maxFiles, accept:["image/*","application/pdf"], maxSizeMb<=25), signature, consent (text with [links](https://...)),
hidden (value only from prefill). Content blocks: heading, text (markdown), divider, image (src),
document (id, title, and src: a PDF link, or body: markdown text; required:true means people must open it, or scroll a body to the end, before they can send).
READ AND CONFIRM ("make sure everyone reads the new policy"): a page with a required document, then a required consent
("I have read and accept the {{...}} policy"), and optionally a signature. get_status shows who read it.
LOGIC: showIf on any block: {"field":"format","op":"eq","value":"Workshop"}. Groups: {"all":[..]}, {"any":[..]}, {"not":{..}}.
Ops: eq, neq, in, not_in, contains, not_contains, gt, gte, lt, lte, empty, not_empty. Choice values match id or label.
Paths: page "next":[{"if":{...},"to":"page_id"|"end"|"end:ending_id"}]; first match wins, else next page.
Endings: "endings":[{"id","title","text","showIf"}]. Piping: {{field_id}}, {{recipient.first_name}} in any text.
ENDINGS (what people see and get after they send). Each ending can also have:
- "blocks":[{"type":"heading"|"text"|"divider"|"image",...}]: content under the text.
- "buttons":[{"label","url","style":"primary"|"secondary","kind":"link"|"booking"|"payment"}], at most 3. https only.
Piping works in the path and the query, never in the host: "https://acme.com/welcome?plan={{plan}}".
kind "booking" adds the person's name and email to Calendly and Cal.com links. kind "payment" adds the email to Stripe Payment Links.
- "summary":true shows the person their answers. "pdf":true adds "Download PDF" (and attaches it to the receipt email).
- "redirect":{"url","after":5}: go to the URL after that many seconds (0-60), with a "Stay here" link.
- "email":{"subject","text"}: send the person a copy of their answers with this text (piping works). See receipt below.
- "next":{"request_id","label"}: the next request or form of a flow. The person continues there, and answers to fields with the same id are prefilled.
- "share":true (forms only): the open link and a QR code, to pass the form on.
- "ai":{"prompt":"Give 3 next steps from their answers"}: AI text from the answers under the ending text (Pro and Business).
- "custom":{"html","css","js"}: a fully custom ending page in the sandbox (same rules as custom forms). In js,
await allback.ready() gives {ending, form, recipient}: ending has title, text, buttons, summary, redirect, pdf, next, ai.
allback.open(url) opens one of the ending's own links (its buttons, redirect, next, pdf). allback.edit() goes back to the form.
Example: "endings":[{"id":"yes","showIf":{"field":"attend","op":"eq","value":"Yes"},"title":"See you there, {{recipient.first_name}}!",
"buttons":[{"label":"Add to calendar","url":"https://acme.com/event.ics"}],"summary":true,"email":{"text":"Thanks! Here is what you told us."}},
{"id":"no","title":"Sorry you cannot make it","redirect":{"url":"https://acme.com/recordings","after":5}}]
Conversational: "welcome":{"text","button"}; page "group":true shows its fields on one screen.
Settings: settings.submitLabel.
CUSTOM DESIGN (optional, only when the user asks for a fully custom look):
- Level 1, "custom":{"css":"…"}: CSS on top of the normal form. Classes: .ab-shell .ab-main .ab-sheet .ab-h1 .ab-lead .ab-q .ab-input .ab-btn .ab-choice-row .ab-error. url() only with data:image. No @import.
- Level 3, "custom":{"html":"…","css":"…","js":"…"}: a fully custom page in a sandbox with no network. Keep the normal blocks: they define the fields, checks, and the "Standard view".
In js use window.allback: await allback.ready() gives {form, fields, answers, recipient, dueAt}; await allback.set(fieldId, value) gives {ok, error};
await allback.upload(fieldId, file); await allback.documentRead(docId); await allback.submit() gives {ok, errors, ending}.
Images only as data: URIs. No fetch, no outside scripts, no links away.
Example js: allback.ready(({answers}) => { name.value = answers.full_name || ''; }); btn.onclick = async () => { await allback.set('full_name', name.value); const r = await allback.submit(); out.textContent = r.ok ? r.ending.title : Object.values(r.errors).join(' '); };
GOOD PRACTICE: keep forms short. Ask only what the sender needs. Use prefill for anything the sender already knows.
Use field ids that match the sender's sheet columns, so add_recipients can prefill from a CSV.
create_form