Skip to Content

Forms

Agents can collect structured data mid-conversation (book a demo, make a reservation, capture a lead). The agent decides when to show a form and can voice-fill fields; your UI renders and submits.

How a form surfaces

  1. The agent sends a data message (e.g. on topic form.book-demo). The SDK matches it against the appearance’s forms and emits form:show with the FormDefinition, an initial draft, the stepIndex, and whether a call is active.
  2. As the user types, call client.updateFormValues(values). The core debounces a form.state publish so the agent always sees current values (and can fill remaining fields by voice).
  3. When the agent fills fields, the SDK emits form:update — re-render from client.getActiveForm().
  4. client.submitForm() validates. On failure → form:validation (and a form_submit_failed message to the agent so it doesn’t claim success). On a multi-step form it advances; on the final step it POSTs and emits form:submitted + a confirmation message to the agent. On network failure → form:error.
  5. The form auto-closes shortly after success (form:close).

FormDefinition

interface FormDefinition { id: string; title: string; fields: FormFieldDef[]; // single-page steps?: FormStep[]; // OR multi-step submit_url?: string; // external endpoint; omit → managed storage submit_method?: "POST" | "PUT" | "PATCH"; success_message?: string; confirmation_type?: string; // message type sent to the agent on success confirmation_topic?: string; // default "voice.user_text" // …layout / labels } interface FormFieldDef { name: string; label: string; type: "text" | "email" | "tel" | "textarea" | "select" | "radio" | "checkbox" | "number" | "date" | "time" | "display"; required?: boolean; options?: FormFieldOption[]; // select / radio / checkbox min?: number; max?: number; pattern?: string; }

Submission

  • Managed storage (no submit_url): POST {apiUrl}/api/agents/{slug}/form-responses/ with { form_id, form_data: [{ name, label, value }], session_id? }. The platform stores it and charges one form-submission billing event. The x-api-key (if configured) is attached only on this first-party path.
  • External (submit_url set): POST/PUT/PATCH the raw values to your endpoint. No platform key is ever sent to a third-party URL.

Where do form definitions come from? They live in the agent’s appearance.forms (served by the appearance endpoint). You can also define them in code and call client.openForm(definition) directly.

Validation

validateFields runs in the browser before any submit: required fields, email / phone / number formats, min / max, and custom pattern. On a multi-step form, an error on an earlier step jumps the user back to it.

Rendering forms in a custom UI

client.on("form:show", ({ definition, draft, stepIndex }) => renderFields(definition, draft, stepIndex), ); // Push on-screen edits into the core model on every input. input.addEventListener("input", () => client.updateFormValues(readMyFormValues()), ); submitBtn.onclick = () => { client.updateFormValues(readMyFormValues()); // capture before submit client.submitForm(); }; client.on("form:validation", ({ errors }) => showInlineErrors(errors)); client.on("form:update", () => rerender(client.getActiveForm())); client.on("form:submitted", ({ successMessage }) => showSuccess(successMessage)); client.on("form:error", ({ message }) => showError(message)); client.on("form:close", () => hideForm());

The core handles validation, multi-step navigation, the form_submit_failed and confirmation messages back to the agent, and the managed-storage POST. You never touch the network for forms.

Opening a form in code

You don’t have to wait for the agent — open a form yourself:

client.openForm( { id: "lead", title: "Get in touch", fields: [ { name: "name", label: "Name", type: "text", required: true }, { name: "email", label: "Email", type: "email", required: true }, ], }, { email: user.email }, // optional prefill draft );
Last updated on