form part to collect text, choices, and dates across pages in a native sheet.
Send a form
Use an Agent Token and an existing chat ID. This example sends ordinary text followed by a form with two pages, an introduction, and a summary.- Preview
- Code
202 after storing the Message. Save its ID to associate the answer with this form, and reuse the same request and idempotency key after an uncertain send.
received_message.title when supplied, otherwise form.title. Tapping it opens the sheet. Each page uses its page title.
The user opens the introduction, fills the pages in order with Next and Back, reviews the summary, and presses Send. The answered bubble reads Form sent; tapping it or reopening the prompt shows the answers read-only.
Choose fields and limits
Each page has anid, a title, and 1 to 50 fields. Page IDs are unique within the form and contain 1 to 19 characters; field IDs are unique across all pages and contain 1 to 100 characters. Both use ^[A-Za-z0-9][A-Za-z0-9._:-]*$.
Limits count Unicode scalar characters. Each field requires a
label with no leading or trailing spaces that is not only spaces or invisible characters such as a zero-width space; titles follow the same rule. placeholder is optional text shown in an empty field. required defaults to false; set it to true to require a nonempty answer before advancing or sending.
Text max_length defaults to 30 for single-line text and 300 for multiline text. These are defaults: set a positive integer max_length to choose another length, such as the 500-character Notes field above. Single-line answers contain no line breaks. An explicit max_length can be at most 9007199254740991, the API’s safe-integer bound. Only text fields take max_length.
A text field’s keyboard chooses the keyboard the app shows: default, email, phone, number or url. Relay also checks two of them. An email answer must be an email address, and a phone answer must be an E.164 number such as +13135550123; any other answer is refused with 2006. number and url only choose the keyboard.
Select and picker options have only value and label. An option’s stable, case-sensitive value contains 1 to 100 characters using the field-ID pattern, and must be unique within that field; its label contains 1 to 30 characters.
A date field’s calendar runs from min_date to max_date, both YYYY-MM-DD. They default to 1900-01-01 and 2100-12-31, and min_date must not be after max_date. An answer outside the range is refused.
Form presentation
Only an agent sends a form, and each Message carries at most one. Only text may sit beside a form: a Message with a form and media, a link, a place, a
data card, buttons, a selection, a rich card or a carousel is refused with 400. Send those in their own Messages. Text beside the form is an ordinary message shown above the card.
Receive answers
Readanswers by field ID, together with reply_to, rather than parsing the visible text. Your existing signed webhook or WebSocket handler receives an ordinary message.received event.
This excerpt shows the answer to the example above. The submission has exactly two ordered parts: the plain text Form sent, then form_response metadata. reply_to.part_index is 1 because the source Message has text at index 0 and the form at index 1.
updates does here.
Omit optional unanswered fields; an empty string or empty array of the appropriate answer type is also accepted. Required fields must have a nonempty answer. The server checks field IDs, value types, lengths, dates, and choices against the source form.
Use the SDK’s event type to keep the structured values intact:
Application handler
Read whether someone answered
A read-backform part includes its definition plus value, has_responded, answers, and reactions: null. value is the form’s received_message.title, else its title: the words a client that does not draw the form shows in its place. The answer fields belong to the authenticated viewer.
- Before a user answers,
has_respondedisfalseandanswersisnull. - After the user answers,
has_respondedistrueandanswersholds that user’s field-keyed values. Amessage.upsertedfor the prompt refreshes the user’s other devices. - If the answer Message no longer exists,
answersisnull. - For an agent,
has_respondedis alwaysfalseandanswersis alwaysnull. Read the incomingform_responseMessage to get the user’s answers.
form_response metadata adds no visible text and has no reactions key.
When it fails
The reply must target a form the user can see in the same chat, using bothreply_to.message_id and reply_to.part_index. A swipe-reply that quotes the form card with text only, such as a question about the form, is an ordinary reply: it succeeds and does not answer the form. Only a Message with form_response answers it.
Keep the same body and idempotency key when retrying an uncertain submission. A matching retry returns the original answer.

