Getting started
Getting Started with Veform

Extremely customizable conversational forms

Veform lets users complete forms with pure audio conversations. It can be used in any web, iOS or Android app.

Getting Started

To get started with Veform you running you need to:

  • Setup secure authentication for end users.
  • Import the Veform library into your app.
  • Setup conversation flows inside your app.
Tip: you can always jump into the playground to experiment interactively and see if Veform is a good fit for your app.

Authentication

Requirements:
Veform API key - contact us to discuss setting one up.
Backend Api endpoint - a secure endpoint that hits the veform api with your key to generate a short lived token.

See the Authentication Example to see how to set up authentication.
Tokens are short lived and single use, each conversation needs a new token.

Importing the Veform library

Can be setup with npm:

npm install veform-js

Or you can use the browser version:

https://unpkg.com/veform-js/dist/veform.browser.min.js
Veform Library
Using the Veform Library

The `veform` package is a library that powers conversations on top of your forms. It provides utility functions to catch conversational events and control conversation flow.

Veform provides methods to programmatically control the conversation, but it works best when you configure the conversation in the form builder and let veform handle the conversation flow.

Veform Class

new Veform(config: VeformConfig, builder: VeformBuilder)

The `Veform` class is the main entry point for running conversational experiences on top of a form. It takes a form definition and connects to the server, provides helper functions to control the conversation and exposes lifecycle events through a set of `on_____` callbacks.

VeformConfig

The configuration for the Veform instance.

Parameters

  • voice?: VoiceOptions - The voice to use for the conversation. Will use default voice if not provided.
VoiceOptions

The options for the voice to use for the conversation.

Parameters

  • voice?: string - The voice to use for the conversation. Will use default voice if not provided.
  • language?: string - The language to use for the conversation. Will use default language if not provided.

Voice options:

  • female-1
  • female-2
  • male-1
  • male-2

Language options:

  • en-US
  • en-GB
  • en-AU

Methods

start(token: string): Promise<boolean>

Establishes microphone access and starts the conversation

Parameters

  • token: string - Access token for the conversation, can be the token itself or the URL you setup to provide the token (see Setting up authentication).
stop(): void

This method should not be needed in most cases.

Ends conversation and cleans up related resources. Veform conversations will naturally stop when the user completes the form or indicates they want to stop.

onLoadingStarted(cb: () => void)

Called after `start()` has obtained microphone access and is setting up connections.

onRunningStarted(cb: () => void)

Invoked when the conversation is live and initial audio has begun being output to the user.

onFocusChanged(cb: (previousName: string, nextName: string) => boolean)

Triggered when the conversation intends to move from one field to another. If the callback returns true, `Veform` blocks the focus change and logs that it was interrupted.

If not blocked, `Veform` finds the next field in your form and, if that field has its own `eventHandlers.onFocus`, invokes it with the previous field name.

onFieldValueChanged(cb: (fieldName: string, value: string | number | boolean) => void)

Called after the value for a field has been changed.

onAudioInStart(cb: () => void)

Triggered when user speech has been started.

onAudioInEnd(cb: (input: string) => boolean)

Triggered when the user stops speaking. If the callback returns true, Veform will block further processing of this input and will not automatically continue the conversation.

When you interrupt in this way, you are responsible for resuming the flow by calling changeField or emitAudio manually.

onAudioOutStart(cb: (chunk: string) => boolean)

Triggered before a new segment of audio is played to the user. If the callback returns true, `Veform` will block that audio from being emitted and from progressing the conversation.

When blocking, you must manually decide how to continue, typically via changeField or emitAudio.

onAudioOutEnd(cb: () => void)

Triggered when audio output has finished playing.

onError(cb: (error: string, critical: boolean) => void)

Called when recoverable errors are encountered.

Parameters

  • error: string - The error message.
  • critical: boolean - Whether the error is critical, veform will usually recover from non-critical errors, if critical the conversation will be stopped by the server.
emitAudio(audio: string, interrupt: boolean): void

This method should not be needed in most cases, you should configure needed output in the form builder.

Emits audio to the user. If interrupt is true this will cut current output and play immediately, otherwise it will be queued behind existing output.

Parameters

  • audio: string - The text or identifier representing the audio to emit.
  • interrupt?: boolean - When `true`, should interrupt any current output and play immediately; otherwise, it is expected to be queued behind existing output.
changeField(fieldName: string): void

This method should not be needed in most cases, you should configure how fields flow between each other in the form builder.

Programmatically moves the conversation focus to a specific field in the form. This will cut current output and play the new field's question.

Can be paired with emitAudio('we have to go back to ...', true) to interrupt current speech and provide context before jumping to the new field.

Parameters

  • fieldName: string - The name of the field to move the conversation focus to.
Veform Builder
Using the Veform Builder

VeformBuilder is a class that helps you build and edit the conversation flow for your form.

VeformBuilder Class

new VeformBuilder()

Methods

addField({name, question, type}: {name: string, question: string, type: FieldType}): Field | null

Adds a new field to the builder based on the provided field's name, question, and type. Field names must be unique to the form.

Parameters

  • name: string - The unique name of the field to add.
  • question: string - The question to ask the user.
  • type: FieldType - The type of field to add.

Returns

  • Field - The field instance that was added.
  • null - If a field with the same name already exists or the name/question are invalid.
getField(name: string): Field | undefined

Retrieves a field from the builder by its name.

Parameters

  • name: string - The unique name of the field to look up.

Returns

  • Field - The matching field instance, if found.
  • undefined - If no field with that name exists.
getFields(): Field[]

Returns the list of all fields currently in the builder, in the order they were added.

removeField(name: string): boolean

Removes a field from the builder by name.

Parameters

  • name: string - The name of the field to remove.

Returns

  • true - If the field was found and removed.
  • false - If no field with that name exists.

Field Class

new Field(name: string, question: string, type: FieldType)

The `Field` class is the base class for all fields in the form. You can visualize a field as a node on the "graph" that represents your form conversation. Each field contains info about its own state and how to navigate between other fields based on user input.

You should not be creating `Field`s directly, instead use the `addField` method on `VeformBuilder` to orchestrate them correctly.
builder.addField({
    name: "name", 
    question: "What is your name?",
    type: FieldType.TEXT
}).onChange((value) => {
    updateMyFormState("name-field", value)
});
                        

Parameters

  • name: string - The unique name of the field to add.
  • question: string - The question to ask the user.
  • type: FieldType - The type of field to add.

Methods

addBehavior(event: FieldEvent, behavior: FieldBehavior): Field

Adds a behavior to the field for a specific event.

Parameters

Returns

  • Field - The field instance that was added.
onFocus(callback: (previousName: string) => void): Field

Sets a callback to be invoked when the field is focused.

Parameters

  • callback: (previousName: string) => void - The callback to invoke when the field is focused, will be called with the name of the previous field.
onChange(callback: (value: string | number | boolean) => void): Field

Sets a callback to be invoked when the field's value changes.

Parameters

  • callback: (value: string | number | boolean) => void - The callback to invoke when the field's value changes.
addValidation(validation: FieldValidation): Field

Adds validation to the field. Validation behaves differently based on the field type. Consult the FieldType Table for more details.

FieldType
type behavior value validation
text Single-line free-form text question. string TextFieldValidation
number Numeric question number NumberFieldValidation
textarea Multi-line free-form text string TextAreaFieldValidation
select Single choice from a set of options. Behaviors can be attached to each option. string SelectFieldValidation
multiselect Multiple choices from a set of options. string MultiselectFieldValidation
yesNo Yes/No question boolean YesNoFieldValidation
intent Placeholder for non-answer intents. N/A IntentFieldValidation
info Informational message only. N/A N/A
FieldEvents Enum

Enum of events a field can subscribe to.

  • validAnswer - Triggered when the field's value is valid.
  • invalidAnswer - Triggered when the field's value is invalid.
  • endRequested - Triggered when the user wants to end the conversation at this field.
  • validYesAnswer - Triggered when the user answers yes to a yes/no question.
  • validNoAnswer - Triggered when the user answers no to a yes/no question.
FieldBehavior
  • moveTo - Moves the conversation focus to a specific field, outputs question for that field.
  • output - Outputs a string to the user.
  • end - Ends the conversation.
TextFieldValidation: `addValidation(validation: TextFieldValidation)`

addValidation parameters for a text type field.

Fields:

  • validate - Whether to strictly validate the field's input. If true nonsensical or unrelated input will be rejected and clarification will be requested.
  • pattern - The pattern to enforce on the text input.

Strict Validation:

If validate: true we will not accept input that does not match the pattern

Patterns:

When a pattern is provided the return value will be only the string that matches the pattern. If the user says "I think my email is doghouse @ gmail" the returned value will be "doghouse@gmail.com". This is not strictly enforced if validate: false and we will return the user's input if unable to parse a value that matches the pattern.

  • email - Returns extracted valid email.
  • phone - Returns extracted phone number.
  • url - Returns extracted URL.
  • date - Returns extracted date in YYYY-MM-DD format.
  • name - Returns extracted name.
TextAreaFieldValidation: `addValidation(validation: TextAreaFieldValidation)`

addValidation parameters for a textarea type field.

Fields:

  • validate - Whether to strictly validate the field's input. If true nonsensical or unrelated input will be rejected and clarification will be requested.
  • maxCharacters - The maximum number of characters the user can enter.
  • minCharacters - The minimum number of characters the user can enter.
Validation for text & textarea types is a bit different from traditional forms. Traditionaly a user can put anything into an `input` or `textarea` element. If you set `validate: true` we will only accept related input. For example if the question is "What was your favorite dish?" and the user says "uhhhh time for baseball." we would ask them to clarify their answer. If `validate:false` we will just return any input as valid.

Think about your desired UX when setting this up, it can be frustrating to users if they feel overly restricted.
NumberFieldValidation: `addValidation(validation: NumberFieldValidation)`

addValidation parameters for a number type field.

Fields:

  • validate - Validate does not do anything for number fields, we can either parse a number or not.
  • minValue - The minimum value the user can enter.
  • maxValue - The maximum value the user can enter.
SelectFieldValidation: `addSelectOption(option: SelectOption)`

Validation for select type fields. Different from other fields in that the answer is either an option from the list or not.

Select Option Parameters:

  • label: string - The label of the option.
  • value: string - The value of the option.
  • readAloud?: boolean - Whether to read the option aloud to the user.
  • behaviors?: FieldBehavior[] - Behaviors to attach to the option.

Example:


builder.addField({name: "job-type", question: "Which role are you applying for?", type: FieldType.SELECT})
.addSelectOption({
    label: "Software Engineer", 
    value: "software-engineer", 
    behaviors: [
        {type: FieldBehaviorType.MOVE_TO, moveToFieldName: "engineer-welcome-info"},
    ]
})
.addSelectOption({
    label: "Designer",
    value: "designer",
    behaviors: [
        {type: FieldBehaviorType.MOVE_TO, moveToFieldName: "designer-welcome-info"},
    ]
})
.addSelectOption({
    label: "None",
    value: "none",
    behaviors: [
        {type: FieldBehaviorType.END},
    ]
})
                        
MultiselectFieldValidation: `addSelectOption(option: MultiselectOption)`

Validation for multiselect type fields.

Multiselect Option Parameters:

  • label: string - The label of the option.
  • value: string - The value of the option.
  • readAloud?: boolean - Whether to read the option aloud to the user.
YesNoFieldValidation: `addValidation(validation: YesNoFieldValidation)`

Validation for yes/no type fields.

Fields:

  • validate - Does not do anything for yes/no fields, we can either parse a yes or no or not.
IntentField

Intent type fields do not return a value but let you check in on users without requiring a specific answer. They are useful for validating past inputs and naturally ending conversations or sections of a conversation.

Example:


builder.addField({name: "check-in", question: "Do you have anything else to add?", type: FieldType.INTENT})

Builder.addField({name: "check-answer-change", question: "Do you want me to repeat any of your answers?", type: FieldType.INTENT})
                        
Examples
Examples of Veform in Action
Authentication
Text Patterns

Setting up authentication for a conversation.

Backend:


app.post('/conversation-token', (req, res) => {                     
    const response = await fetch(`https://app.veform.co/veform-api/token`, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${process.env.VEFORM_API_KEY}`,
        },
    });
    const data = await response.json();
    res.json({token: data.token});
});
                    

Frontend:


const builder = new Veform.VeformBuilder();
const veform = new Veform.Veform(builder);
const token = await fetch('https://mybackend.com/conversation-token', {
    method: 'POST',
}).then(response => response.json()).then(data => data.token);
veform.start(token);
                    

<div class="container">
    <button id="start" class="start">Start conversation</button>
    <label for="name">Name</label>
    <input id="name" type="text"/>
    <label for="email">Email</label>
    <input id="email" type="email"/>
    <label for="phone">Phone</label>
    <input id="phone" type="tel"/>
    <label for="url">Website</label>
    <input id="url" type="url"/>
    <label for="date">Date</label>
    <input id="date" type="date"/>
</div>

<script>
    const builder = new Veform.VeformBuilder();
    builder.addField({name: 'name', question: 'What is your name?', type: 'text'}).addValidation({
        pattern: "name"
    })
    builder.addField({name: 'email', question: 'What is your email?', type: 'text'}).addValidation({
        pattern: "email"
    })
    builder.addField({name: 'phone', question: 'What is your phone number?', type: 'text'}).addValidation({
        pattern: "phone"
    })
    builder.addField({name: 'url', question: 'What is your website?', type: 'text'}).addValidation({
        pattern: "url"
    })
    builder.addField({name: 'date', question: 'What is the date?', type: 'text'}).addValidation({
        pattern: "date"
    })
    builder.addField({name: 'goodbye', question: 'Thanks for your time, Goodbye!', type: 'info'});
    const veform = new Veform.Veform(builder);
    veform.onFocusChanged((previous, next) => {
        document.getElementById(next).focus();
    });
    veform.onFieldValueChanged((fieldName, value) => {
        document.getElementById(fieldName).value = value;
    });
    veform.start('https://mybackend.com/conversation-token');
</script>