Voice answer
The respondent records a spoken answer, with the subtopics shown as what to try to cover. Prefer this over long_text when the author wants a spoken, reflective or story-style answer. Chatform transcribes it afterwards and keeps the recording, the transcript, a summary and what they said about each subtopic. The subtopics are what make this block useful, so always give it 2 to 4, drawn from what the question is really asking.
voiceThe respondent records a spoken answer, with the subtopics shown as what to try to cover. Prefer this over long_text when the author wants a spoken, reflective or story-style answer. Chatform transcribes it afterwards and keeps the recording, the transcript, a summary and what they said about each subtopic. The subtopics are what make this block useful, so always give it 2 to 4, drawn from what the question is really asking.
How it gets answered: recorded or typed by the respondent; a recording is stored at once and transcribed afterwards, never extracted.
Configuration
Fields specific to voice. The fields every block has (id, ref, title, required, visibility, media, agentHints, prefillParam, identityField) are documented once.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"buttonLabel": {
"type": "string",
"maxLength": 60
},
"type": {
"type": "string",
"const": "voice"
},
"points": {
"maxItems": 6,
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"minLength": 6,
"maxLength": 32
},
"key": {
"type": "string",
"pattern": "^[a-z][a-z0-9_]{0,30}$"
},
"label": {
"type": "string",
"minLength": 1,
"maxLength": 80
}
},
"required": [
"id",
"key",
"label"
]
}
},
"maxSeconds": {
"default": 180,
"type": "integer",
"minimum": 30,
"maximum": 600
}
},
"required": [
"id",
"ref",
"title",
"type"
],
"$defs": {
"__schema0": {
"type": "object",
"properties": {
"op": {
"type": "string",
"enum": [
"and",
"or"
]
},
"conditions": {
"default": [],
"type": "array",
"items": {
"type": "object",
"properties": {
"left": {
"oneOf": [
{
"type": "object",
"properties": {
"kind": {
"type": "string",
"const": "ref"
},
"ref": {
"type": "string",
"pattern": "^[a-z][a-z0-9_]{1,40}$"
}
},
"required": [
"kind",
"ref"
]
},
{
"type": "object",
"properties": {
"kind": {
"type": "string",
"const": "variable"
},
"name": {
"type": "string",
"pattern": "^[a-z][a-z0-9_]{0,40}$"
}
},
"required": [
"kind",
"name"
]
},
{
"type": "object",
"properties": {
"kind": {
"type": "string",
"const": "hidden"
},
"name": {
"type": "string",
"pattern": "^[a-zA-Z_][a-zA-Z0-9_.-]{0,60}$"
}
},
"required": [
"kind",
"name"
]
},
{
"type": "object",
"properties": {
"kind": {
"type": "string",
"const": "literal"
}
},
"required": [
"kind"
]
}
]
},
"op": {
"type": "string",
"enum": [
"eq",
"neq",
"gt",
"gte",
"lt",
"lte",
"contains",
"not_contains",
"starts_with",
"ends_with",
"matches_regex",
"is_empty",
"is_not_empty",
"is_checked",
"is_not_checked",
"includes",
"not_includes",
"ranked_above",
"ranked_below"
]
},
"value": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "array",
"items": {
"type": "string"
}
}
]
}
},
"required": [
"left",
"op"
]
}
},
"groups": {
"default": [],
"type": "array",
"items": {
"$ref": "#/$defs/__schema0"
}
}
},
"required": [
"op"
]
}
}
}In a generated draft these arrive as config pairs: points=<label|label|label> (the subtopics: 2 to 4 short questions or topics a good answer covers, up to 6; omit only when the author asks for a completely open answer), maxSeconds=<30-600, the longest recording>.
What you receive
GET /v1/forms/{id} and every "next question" projects blocks through toPublicBlock. For the example above:
{
"id": "blk_voice001",
"ref": "q_voice",
"type": "voice",
"title": "Tell us about yourself",
"required": true,
"imageKey": null,
"media": null,
"points": [
{
"id": "pt_goal001",
"key": "goal",
"label": "the goal"
}
],
"maxSeconds": 180
}What you send
Typed text, or a recording. Send a string or { text, via: "typed" }; a recording is uploaded to the session first and sent as { audio: { fileId }, via: "audio", seconds }. A recording is stored with status: "pending" and transcribed afterwards, when chatform writes text, summary, points and status: "done" (or "failed", the recording kept). summary and points sent by a client are dropped.
type Answer = string | { text: string; via: "typed"; seconds?: number } | { audio: { fileId: string }; via: "audio"; seconds: number };curl -X POST https://api.chatform.in/v1/responses/{RESPONSE_ID}/answers \
-H "x-api-key: $CHATFORM_SECRET_KEY" \
-H "content-type: application/json" \
-d '{"ref": "q_voice", "value": " I want to ship a form. "}'What gets stored
The value is normalised before it is saved, so what you read back is not always what you sent.
| You send | Stored | |
|---|---|---|
" I want to ship a form. " | {"text":"I want to ship a form.","via":"typed"} | |
{"text":"I want to ship a form.","via":"typed","seconds":12,"summary":"ignored"} | {"text":"I want to ship a form.","via":"typed","seconds":12} | summary and points from a client are dropped |
In responses and exports
A recorded answer is stored the moment the respondent moves on, as { via: "audio", seconds, audio: { fileId, r2Key, mime, seconds }, status: "pending" }. Chatform then transcribes it and writes text, summary, points and status: "done", or status: "failed" with the recording kept. A typed answer is { text, via: "typed" } with no status. Webhooks and /v1 carry the record as is: a response finished before its recording was transcribed is delivered with status: "pending", and the transcribed answer follows as a response.answer_recorded event. Its display text is the transcript, or Voice answer 1:24 until there is one.
points maps each point's key to what the respondent said about it, or null when they did not cover it. An answer with no points was never analysed, which is different from one that covered nothing. A plain string, from a long_text question converted to voice, is read as the transcript with no summary.
CSV, XLSX and the spreadsheet feed put the transcript in the question's own column, then <question> · Recording (a link to the recording, which asks whoever opens it to sign in to the form's workspace), <question> · Summary, and one <question> · <point label> column per point, empty where the answer did not cover it. The results screen shows the same columns.
Errors
| Code | When | Message |
|---|---|---|
required | "" | This question needs an answer. |
type | 42 | Please answer with text. |
too_long | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" | Too long: please keep it to 5000 characters. You have 5001. |