How it fits together
There are three parties in every session:
- The respondent speaks their answers.
- The agent asks the questions, understands the answers, checks them against each field's rules, and hands over clean values.
- The form is separate from the agent and stays in charge. On an AmphiVox form it is the form page itself; on a website it is your page; with the extension it is the Google or Microsoft form being filled.
The agent never owns the form. It does not submit it, it does not navigate it by itself, and it does not decide what the form holds. It asks; the form shows what it actually holds; the agent believes the form.
AmphiVox runs in three places:
| Surface | What it is | Who connects it |
|---|---|---|
| AmphiVox Forms | A form built or imported in AmphiVox and shared as a link or a QR code. Respondents answer it on amphivox.com, by voice or by typing. | Nobody: AmphiVox provides both the agent and the form. |
| AmphiVox Widgets | A voice button on your own site, added with widget.js or the @amphivox/react package. | You, the site owner. |
| AmphiVox Extension | The AmphiVox browser extension for Chrome and Edge (coming soon), which fills Google Forms and Microsoft Forms in the respondent's own browser. | Nobody: the respondent installs it. |
On an AmphiVox form there is nothing to connect. AmphiVox's own form page is the form side of everything below, and it follows every rule described here. When the conversation completes on a form with no fields the respondent fills in by hand, that page submits the answers itself; otherwise the respondent submits. The agent never does.
The rest of this page is written for developers connecting the widget, but it applies to all three. Where something differs, the page says which.
Words used on this page
Session. One voice conversation, from the moment the respondent starts talking to the moment it ends.
Field. One question on the form. Every field has an id (a stable name like full_name, which code uses), a label (the question as the respondent reads it), and a type (what kind of answer it takes).
Page. Forms can have several pages. Pages are numbered from 0. The agent only works on the page that is on screen.
Page snapshot. A report from the form to the agent: which page is showing, and what every field on it holds right now.
Write. The agent handing the form a value to put in a field. Every write carries a write id, a label that identifies that one attempt.
Refusal. The form telling the agent "I didn't take that value", with the reason.
Skip. The respondent choosing not to answer an optional question.
Manual field. A field the agent never fills by voice. The respondent fills it in on the form themselves.
Ending. How a session finished: completed, handed back to the respondent, timed out, stopped, or failed.
Field types
Every field has one of these types. The value has the same shape in both directions: what the agent writes, and what the form reports back in a snapshot.
| Type | Asks for | Value | Available on |
|---|---|---|---|
str | One line of text | string | all |
long | Longer text, several lines | string | all |
int | A whole number | number | all |
float | A number that can have decimals | number | all |
bool | Yes or no | true / false | AmphiVox forms, widget |
date | A calendar date | "DD-MM-YYYY", e.g. "07-03-2026" | all |
time | A time of day | "HH:MM", 24-hour, e.g. "14:30" | extension |
duration | A length of time | "HH:MM:SS", e.g. "01:30:00" | extension |
email | An email address | string | all |
phone | A phone number | E.164 string, e.g. "+919876543210" | AmphiVox forms, widget |
enum | One choice from a list | one option string, exactly as declared | all |
enum with multi | Any number of choices from a list | array of option strings | all |
ranking | Put every item of a list in order | array of all the options, most preferred first | extension |
address | A postal address | object of parts, e.g. { "street": "…", "city": "…", "zip": "…", "country": "IN" } | AmphiVox forms, widget |
url | A link | never asked by voice, see below | all |
file | A file upload | never asked by voice, see below | all |
legal | Accepting terms or giving consent | never asked by voice, see below | AmphiVox forms, widget |
"All" means AmphiVox forms, the website widget and the browser extension. The extension's time, duration and ranking come from question types Google Forms and Microsoft Forms have; neither an AmphiVox form nor a widget schema can declare them. address, bool, phone and legal have no equivalent question type on those platforms: a phone or yes/no question there reaches the agent as plain text or a choice.
Text collected by voice is in Latin script. Whatever language the respondent speaks, names are romanised and free text is translated to English before it is stored. An answer the respondent types on the form stays as typed, in whatever script they used.
Rules a field can carry
Every rule is optional. When one is left out, its default applies.
| Rule | Applies to | Default | What it does |
|---|---|---|---|
required | any type | false | The respondent must answer before the form can be finished. |
pii | any type | false | Marks personal data. The agent never asks for it by voice; the respondent fills it in on the form. |
desc | any type | none | Extra context the agent can use when asking (up to 300 characters). |
options | enum, ranking | none (required) | The exact option strings, up to 30. Each is trimmed and cut to 100 characters; a blank one is dropped. |
multi | enum | false | true lets the respondent pick more than one. |
maxLength | str, long | AmphiVox forms and widget: 150 (str), 1000 (long). Extension: the form's own limit, if it sets one (Microsoft Forms caps every text answer at 4000 anyway) | The longest answer accepted, in characters: 50 to 5000. A long answer is stored in full, every point the respondent made; it is condensed only when it would exceed this limit. |
min / max | int, float, duration | none | The smallest and largest number accepted. Both ends are included: min: 1, max: 10 accepts 1 and 10. When min equals max, that one number is the only accepted answer. Whole numbers for int. For duration, they limit the hours. |
region | phone | "US" | The country (ISO code such as "IN") used to read a number spoken without a country code. |
components | address | none (required) | Which parts to ask for: street, apt, city, state, zip, country, each with its own optional desc and required. A required country is always added; its default (an ISO code) is the country assumed when the respondent doesn't say. |
maxSizeMb / extensions | file | 10 MB / any | The largest file and the allowed file types, checked by your form. |
What you can rely on in a value
- Values arrive in one standard shape. A date is always
DD-MM-YYYY, a phone number always E.164, a choice always one of the option strings exactly as declared, never the respondent's own wording of it. - Rules are checked before a value reaches you. A value the agent writes already satisfies the field's
options,maxLengthandmin/max. - Lists are always complete. A multiple-choice value is the whole selection, not just what changed, and a ranking always contains every option exactly once. Treat the array as the final state: untick anything no longer in it.
Fields the agent never asks for
Some fields are never filled by voice. The agent tells the respondent to fill them in on the form. On a page with more pages after it, it waits until the form shows them filled, then moves on. On the last page it does not wait: it tells the respondent what is left and ends. These are:
file,urlandlegalfields;- any field marked
pii; - on the extension, any question it can't fill reliably (a question type it doesn't recognise, for example). Where the extension can't read such a question either, nothing on the form can show it filled, so the agent accepts the respondent saying it is done.
The agent never learns their contents. It only ever learns that the field has an answer, never what it is. On AmphiVox forms and the widget, the value is replaced with a plain "filled in" marker before anything leaves the browser. On the extension, a link typed into a link question is sent with the page's other answers and reduced to that marker on arrival.
Because of that, only report one of these fields as filled when your form would accept its value. If an invalid link is reported as filled, the agent moves on while your form still refuses to continue. For a file field, check the file against maxSizeMb and extensions and store it first, then report it as uploaded.
Pages
A form can have several pages. The agent only asks about fields on the page that is on screen. Answers from earlier pages are kept, but the agent can't change them, because they are no longer on screen to change.
Pages are numbered 0, 1, 2…, with no gaps. The last page is the one that submits.
Page snapshots
The form tells the agent what it holds by sending a page snapshot: the page number on screen, and the value of every field on that page.
{
"page": 0,
"values": { "full_name": "Asha Rao", "email": null, "newsletter": null }
}
- Always the whole page, never just what changed. If one snapshot goes missing, the next one fixes everything.
- Exactly the page's fields. A snapshot that leaves out one of the page's fields, or names a field from another page, is rejected.
nullmeans no answer. So do an empty string and an empty list.- Send one before the first session, after every change, and on every page change. That includes changes the respondent makes by typing.
- The form is the truth. A write is only a request; the next snapshot is the fact. If the respondent edits a field the agent filled, the agent goes with the edit.
Before turning the page, and before finishing, the agent asks for one fresh snapshot, so that the respondent's last edit on that page is included.
Known answers
Before a session starts, the form can give the agent answers it already has, from any page, keyed by field id: a returning respondent's details, say, or answers collected earlier. The agent doesn't ask for those again.
- Same shapes, same checks. Known answers use the value shapes above and are checked like a snapshot: an invalid one is dropped, and the agent asks for that field as usual. A key that is not one of the form's field ids is different: it rejects the whole set with
widget_page_invalid. - They can carry skips. A skip marker among them keeps an earlier skip.
- They apply when a session starts. Changing them affects the next session, not one already running.
On an AmphiVox form, the form keeps a respondent's unfinished answers in their browser for up to a day. If they come back and choose to resume, those answers are the known answers. On the extension there is nothing to send: what the form already shows is the known answers.
Writes
When the respondent answers, the agent sends a write: the field id, the value, and a write id.
- Taking the value needs no reply. Put it in the field; your next snapshot confirms it.
- The same write id can arrive twice. Applying it a second time must change nothing.
- A write is always a valid value for the field's type and rules (see What you can rely on in a value).
Refusals
If your form doesn't take a value, refuse the write with its write id and a reason. There are two kinds of refusal, and only you know which one applies:
| Kind | When | What the agent does |
|---|---|---|
validation | Your own rule rejected the answer, one the field's rules can't express ("that email is already registered", "that coupon has expired"). | Tells the respondent your reason and asks for a different answer. The rejected value is never sent again. |
write | The value couldn't be put into the field at all. | Sends the same value once more, without telling the respondent. Any refusal that doesn't say validation is treated this way. |
- Attempts are limited per field. A
writerefusal gets two tries: the value, then the one resend. Avalidationrefusal gets three, each a different answer. After that, the agent stops and asks the respondent to fill that field in on the form. - Rejections are announced together. When you refuse several answers as
validationat once, the agent tells the respondent every reason in one reply and asks for all of them again. - Keep reporting the truth. After a refusal, your snapshots should show whatever the field really holds, the rejected value included. The agent never picks up a value you refused from a snapshot.
- The reason is spoken aloud and kept in AmphiVox's session logs. Keep it short, and never put the rejected value or anything personal in it. Reasons longer than 200 characters are cut.
Skips
A respondent can skip any optional field by saying so. The agent then reports a skip for that field, using a special marker value, "__CLEAR__". Both SDKs give it a name, SKIP, so you never have to type the string.
- A skip is not an answer. Never put the marker in your form, validate it, or submit it. Leave the field as it is.
- It has no write id. Nothing was written, so there is nothing to refuse or confirm.
- To remember a skip in a later session, send the marker back as that field's value, among your known answers or in a snapshot while the field is still empty. Otherwise the next session asks the question again. A real answer the respondent gives later always replaces it.
- Required fields can't be skipped. Sending the marker for one changes nothing.
On an AmphiVox form, skips are remembered with the respondent's unfinished answers. On the extension, skips need nothing from anyone: the form's field is simply left alone.
Page turns
When every field on a page is answered and more pages follow, the agent asks before moving on: "Anything to change before I move on?" If the respondent agrees, the agent asks the form to go to the next page.
- The form runs its own Next. The same checks and navigation as its own Next button, then a snapshot of the new page.
- If Next didn't move, the form reports the unchanged page and says the move was blocked. If a field on the page is still unanswered, or was left for the respondent, that explains it: the agent goes back to that field and carries on. If nothing on the page explains it, the session ends: the agent tells the respondent it is stuck and disconnects, ending as
hands_off. A block the agent can't see the reason for can't be fixed by voice, so it hands the form back rather than guess. Since the agent only asks to move on once the page looks complete, a block from your own rule usually ends the session. - If the form says nothing, neither a new page nor "blocked", the agent waits ~15 seconds and then treats the move as blocked. A form that never handles page turns therefore ends every multi-page session on its first page, after that pause.
- Manual fields hold the page. While a manual field on the page is empty, the agent won't ask to move on. It asks the respondent to fill it in first, or, if it's optional, to say they want to skip it.
Finishing
When everything it can fill is filled, the agent reads the answers back for the respondent to confirm, then ends the session. It never submits the form. Submitting is the form's job, and only after an ending of complete.
Endings
Every session that started ends exactly once, with one of these:
| Ending | What happened | What to do | Extra detail |
|---|---|---|---|
complete | The agent has every answer it needs. A form with any field the agent never asks for, or any field it gave up on after repeated attempts, never ends this way: it ends hands_off, even if the respondent has since filled everything, and outstanding may then be empty. | Run your own checks and submit. | — |
hands_off | The agent filled what it could and is handing the form back: some fields need the respondent, or the agent could not continue. | Don't submit. Let the respondent review and finish on the form. | outstanding: the labels of the fields left to fill, which can be empty |
timeout | The session ended after silence, or reached its time limit. | Let the respondent continue on the form, or start again. | timeout: idle or max_duration |
manual | The respondent stopped the session, or your code closed it. | Nothing. | — |
error | A live session failed. Answers already written stay valid. | Let the respondent continue, or start again. | code and message |
Silence and time limits
These are the current server settings. Treat them as approximate.
| What | When |
|---|---|
| First nudge (the agent repeats the question) | ~15 seconds of silence after the agent stops speaking |
| Second nudge (the agent warns it will disconnect) | ~15 more seconds of silence |
Session ends with timeout: idle | ~15 more seconds of silence (about 45 seconds in all) |
Longest session, ending with timeout: max_duration | 10 minutes |
| A page turn the form never answers counts as blocked | ~15 seconds after the agent asks for it |
| A browser that stops responding is disconnected | after 2 missed checks in a row; checks run every ~4 seconds, starting ~10 seconds into the session |
The silence clock only runs while it is the respondent's turn: not while the agent is speaking or thinking. Muting is not pausing. A muted microphone sounds exactly like a silent respondent.
On the website widget, the voice runs inside an iframe that must stay rendered. Hiding it with display: none makes the browser slow it down until it looks unresponsive and is disconnected; use the SDK's own hidden option instead.
Languages and voices
The agent speaks 45 languages, in two groups. Pass the code; the live list is also available as JSON from GET https://api.amphivox.com/api/voice/languages.
Worldwide. The agent also understands English, so a respondent can switch to English mid-sentence.
| Code | Language | Code | Language |
|---|---|---|---|
en | English | lv | Latviešu |
es | Español | lt | Lietuvių |
fr | Français | no | Norsk |
de | Deutsch | pl | Polski |
it | Italiano | pt | Português |
nl | Nederlands | ro | Română |
ja | 日本語 | ru | Русский |
ar | العربية | sk | Slovenčina |
bg | Български | sl | Slovenščina |
hr | Hrvatski | sv | Svenska |
cs | Čeština | th | ไทย |
da | Dansk | tr | Türkçe |
et | Eesti | uk | Українська |
fi | Suomi | vi | Tiếng Việt |
el | Ελληνικά | zh | 中文(简体) |
hu | Magyar | zh-TW | 中文(繁體) |
id | Bahasa Indonesia | ko | 한국어 |
Bharat. Indian languages; the agent detects which one the respondent is speaking.
| Code | Language |
|---|---|
en-IN | English (India), tuned for Indian accents and names; different from en |
hi | हिन्दी |
ta | தமிழ் |
te | తెలుగు |
kn | ಕನ್ನಡ |
ml | മലയാളം |
bn | বাংলা |
mr | मराठी |
gu | ગુજરાતી |
pa | ਪੰਜਾਬੀ |
od | ଓଡ଼ିଆ |
Voice. The agent speaks with a female voice (female, the default) or a male one (male). Any other value given at start falls back to female; one given when changing the voice later is ignored, and the current voice stays.
The language and the voice are set when a session starts. Changing either one applies to the next session, not the one already running.
Errors (website widget)
The widget reports problems as an error with a code and a message. The message is plain text that is safe to show; decide what to do from the code, and keep a fallback for codes you don't recognise, because this list can grow.
These stop a session before it starts, or reject a message your page sent (a live session carries on after those):
| Code | What happened |
|---|---|
invalid_widget_key | The key doesn't exist, or was deleted. |
origin_not_allowed | Your site isn't on the key's list of allowed websites. |
widget_mode_mismatch | The mode you passed doesn't match the mode the key was created with. |
widget_request_invalid | A voice request carried data the key's mode doesn't accept. Only reachable by calling the API directly. |
widget_session_forbidden | A reconnect named a session that belongs to a different widget key. Only reachable by calling the API directly. |
widget_page_required | No page snapshot was sent before the session was opened. |
widget_page_invalid | A page snapshot didn't match the form's fields. |
widget_payload_too_large | A schema or page snapshot was over the size limit. |
widget_not_ready | A control was used before the widget finished loading. Try again, for example from a click. |
invalid_voice_token / voice_token_expired | The session couldn't be authorised, or took too long to start. Open it again. |
session_not_found | The session a reconnect asked for has already ended. Open a new one. |
initial_state_invalid / initial_state_too_large | The known answers were malformed, or over the size limit. |
session_limit_exceeded / session_busy / session_spawn_failed | Voice capacity is full, or the session couldn't start. Try again shortly. |
token_fetch_failed / connect_timeout / connect_failed / webrtc_unavailable | The connection failed, or this browser can't make voice calls. |
widget_origin_unavailable | The browser didn't say which site the widget is embedded in, so it can't run there. |
Only with a form-linked key (the form is stored in AmphiVox):
| Code | What happened |
|---|---|
widget_schema_changed | The form was edited while the widget was loading. The next open loads the new version and reuses your last page snapshot if it still fits; otherwise send the page again. |
form_not_found | The key's form no longer exists. |
widget_schema_forbidden | A schema was sent, but this key's form is the stored one. Don't send a schema. |
Only with a developer-defined key (your code sends the form):
| Code | What happened |
|---|---|
widget_schema_required | No form yet: none was given, or a snapshot or known answers arrived before a valid one. |
widget_schema_invalid | The form breaks one of the rules above. onError gives this code and a general message. Where the server can name the field and the rule (never a value), that is in the failed request's HTTP response body, not in onError. |
widget_schema_locked | The form was changed while a session was running. The change is discarded; send it again once no session is running. |
A session that started and then failed doesn't report an error; it ends with error. Its code is one of session_ended_unexpectedly, reconnect_failed, voice_service_unavailable, voice_service_busy, voice_service_error or voice_session_error.