Overview

Forms are defined in the character’s appearance configuration under the forms array. When a form is defined, the agent automatically gains a callable tool with the same name. During a call, the agent can:
  1. Open the form panel (optionally pre-filling fields with values it already knows)
  2. Track field changes in real time via the data channel
  3. Guide the user through multi-step flows verbally
  4. Submit the completed form to your endpoint (or to Oshara’s managed storage)

Minimal single-step form

{
  "forms": [
    {
      "id": "contact",
      "title": "Get in touch",
      "submit_url": "https://api.yoursite.com/contact",
      "fields": [
        { "name": "name",  "label": "Full name",  "type": "text",  "required": true },
        { "name": "email", "label": "Email",       "type": "email", "required": true },
        { "name": "message", "label": "Message",   "type": "textarea" }
      ]
    }
  ]
}

Multi-step stepper example

{
  "forms": [
    {
      "id": "book-demo",
      "title": "Book a demo",
      "subtitle": "Tell us about your use case",
      "submit_url": null,
      "submit_label": "Confirm booking",
      "success_message": "We'll be in touch within one business day!",
      "steps": [
        {
          "id": "contact",
          "title": "Your details",
          "fields": [
            { "name": "first_name", "label": "First name", "type": "text", "required": true, "width": "half" },
            { "name": "last_name",  "label": "Last name",  "type": "text", "required": true, "width": "half" },
            { "name": "email",      "label": "Work email", "type": "email","required": true },
            { "name": "company",    "label": "Company",    "type": "text" }
          ]
        },
        {
          "id": "use-case",
          "title": "Your use case",
          "fields": [
            {
              "name": "use_case",
              "label": "What are you trying to build?",
              "type": "select",
              "required": true,
              "options": ["Customer support bot", "Sales assistant", "Internal helpdesk", "Other"]
            },
            { "name": "details", "label": "Tell us more", "type": "textarea", "rows": 4 }
          ]
        },
        {
          "id": "schedule",
          "title": "Preferred time",
          "fields": [
            { "name": "date",     "label": "Preferred date", "type": "date", "required": true },
            { "name": "timezone", "label": "Your timezone",  "type": "text" }
          ]
        }
      ]
    }
  ]
}

FormDefinition schema

FieldTypeRequiredDescription
idstringStable identifier. Used as the tool name and for LiveKit topic matching.
titlestringHeading shown at the top of the form panel.
subtitlestringSubheading / context text below the title.
fieldsFormFieldDef[]one of fields or stepsFields for a single-page form. Ignored if steps is set.
stepsStep[]one of fields or stepsMulti-step stepper. Each step has its own fields array.
submit_urlstring | nullPOST target. Absolute URL or path relative to apiUrl. null = store in Oshara managed storage.
submit_method"POST" | "PUT" | "PATCH"HTTP method for submission (default "POST").
submit_labelstringSubmit button text (default "Submit").
success_messagestringMessage shown in the panel after successful submission.
disabledbooleantrue = form is not exposed to the agent as a tool. Useful to temporarily hide a form.
topicsstring[]Extra LiveKit data-channel topics that trigger this form to open.
event_typesstring[]Legacy event_type aliases for backwards compatibility.
confirmation_topicstringTopic on which the widget echoes a confirmation message after submission (default "voice.user_text").
confirmation_typestringtype field on the confirmation payload (default "{id}_submitted").
layout.field_layout"stack" | "grid""grid" enables two-column layout (use width: "half" on fields).
layout.density"comfortable" | "compact"Spacing density.
layout.label_position"top" | "inline"Label placement.

Step object

FieldTypeDescription
idstringStep identifier.
titlestringStep heading (shown in the stepper header).
subtitlestringStep subheading.
fieldsFormFieldDef[]Fields for this step.
next_labelstring”Next” button label override.
back_labelstring”Back” button label override.

FormFieldDef schema

FieldTypeDescription
namestringField key — used as the key in the submitted JSON object. Not required for type: "display".
labelstringLabel text shown above the field.
typesee belowInput type.
placeholderstringPlaceholder text.
requiredbooleanMarks the field as mandatory (client-side validation).
optionsstring[] | {value, label?}[]Choices for select and radio fields.
rowsnumberNumber of visible rows for textarea.
default_valuestringPre-filled value. The agent can also override this when opening the form.
help_textstringSub-label hint shown below the field.
patternstringRegex pattern for client-side validation.
minnumberMinimum value (for number) or minimum character length (for text fields).
maxnumberMaximum value / length.
width"full" | "half"Column width when field_layout is "grid".

Field types

TypeRenders as
textSingle-line text input
emailEmail input (validated format)
telPhone number input
textareaMulti-line text area
selectDropdown <select>
numberNumeric input
dateDate picker
timeTime picker
checkboxSingle checkbox (value is "true" / "false")
radioRadio button group
displayRead-only text block (no name needed)

Submit behaviour

submit_url: null — managed storage

When submit_url is null, the widget POSTs the form values to:
POST /api/agents/{slug}/form-responses/
You can retrieve submissions via the Form Responses API.

submit_url: "https://..." — direct POST

The widget sends a JSON POST directly to your URL with the body:
{
  "field1": "value1",
  "field2": "value2"
}
The endpoint should return any 2xx status to indicate success.

Confirmation echo

After a successful submission the widget publishes a confirmation message on the LiveKit data channel so the agent knows the form was completed:
{
  "type": "book-demo_submitted",
  "form_id": "book-demo",
  "text": "I have confirmed the booking form submission.",
  "form": {
    "first_name": "Alice",
    "email": "alice@example.com"
  }
}
The agent uses this to continue the conversation naturally (“Great, I’ve got your details — someone will reach out within 24 hours!”).

How the agent opens a form

The agent calls a tool named after your form’s id. For example, with id: "book-demo", the tool invocation looks like:
{
  "name": "book_demo",
  "arguments": {
    "first_name": "Alice",
    "email": "alice@example.com"
  }
}
Any argument matching a field name is used as a pre-fill value. The widget opens the form panel and populates those fields immediately.