Forms (form)
A short general form. Use it only when no more specific page type fits. Do not ask for payment card details, passwords or other credentials, or identity-document numbers: such fields are rejected.
When to use it: You need more than two typed values at once (name, phone, a choice, a note). For one value, ask in chat.
When not to use it: Do not use it for one or two questions (ask in chat), or when a more specific page type fits: dates, times, products or a choice between options.
Gather first:
- Exactly which details you still need; do not ask for what you already know.
- Whether each field is required.
Writing good content:
- Keep it short: ask only what you need now, at most 12 fields. Split longer needs into a later page.
- Pick the
kindthat matches the answer (email,phone,number) so the right keyboard opens, andselectwithoptionswhen there is a fixed list. - Write
labels as plain questions, and usehelpTextfor why you need something or its format. - Never ask for card numbers, passwords or other credentials, or identity-document numbers: these are refused, and a form is not a way around that.
Fields
Every page also has version (always 1), title, and optionally description, branding, cover (a header photo) and expiresAt; see Page fields. These are the fields of page (the page type and its data):
| Field | Description |
|---|---|
fields | object[], required. The fields, in display order (1-12). For more, use several pages. Limits: 1-12 items. |
fields[].id | string, required. Stable identifier you choose. It is returned in the result, so keep it meaningful to you. Limits: matches ^[A-Za-z0-9_-]{1,64}$. |
fields[].label | string, required. Question or field name shown above the input, e.g. "Delivery address". Limits: 1-120 characters. |
fields[].kind | "short_text" | "long_text" | "number" | "email" | "phone" | "select" | "toggle", required. short_text: one line. long_text: several lines. number: numeric keypad. email, phone: matching keyboards. select: dropdown, needs options. toggle: a yes/no checkbox; with required true it must be ticked (e.g. "I agree"). |
fields[].required | boolean. true blocks submit until the field is filled (for a toggle: until it is ticked). Omit or false for an optional field. |
fields[].placeholder | string. Example text shown inside an empty input. Ignored for toggle and select. Limits: 1-120 characters. |
fields[].helpText | string. Optional hint shown under the field. Limits: 1-280 characters. |
fields[].min | number. number only: the smallest allowed value. |
fields[].max | number. number only: the largest allowed value. |
fields[].maxLength | integer. short_text and long_text only: most characters allowed. Defaults to 200 (short_text) or 2000 (long_text). Limits: 1-5000. |
fields[].options | object[]. select only: 2-30 choices. The chosen option id is returned as the value. Limits: 2-30 items. |
fields[].options[].id | string, required. Stable identifier you choose. It is returned in the result, so keep it meaningful to you. Limits: matches ^[A-Za-z0-9_-]{1,64}$. |
fields[].options[].label | string, required. Option text. Limits: 1-120 characters. |
fields[].autocomplete | "name" | "given-name" | "family-name" | "organization" | "street-address" | "postal-code". short_text only: browser autofill hint, e.g. "name" or "postal-code". email and phone are handled automatically. |
Why these are required: kind decides the input and the value type in the result.
When optional fields are left out: The field is optional; no placeholder or hint; text limits default to 200 (short) or 2000 (long) characters.
Minimal example
Only the required fields:
{
"version": 1,
"title": "Tell us about your visit",
"page": {
"type": "form",
"fields": [
{
"id": "name",
"label": "Your name",
"kind": "short_text"
},
{
"id": "email",
"label": "Email",
"kind": "email"
},
{
"id": "feedback",
"label": "What could we do better?",
"kind": "long_text"
}
]
}
}Full example
{
"version": 1,
"title": "Delivery details",
"description": "I need these to schedule your couch delivery.",
"branding": {
"name": "Juniper Concierge",
"accentColor": "#0050d8"
},
"cover": {
"url": "https://images.example.test/couch.jpg",
"alt": "A green three-seat couch in a bright living room"
},
"page": {
"type": "form",
"fields": [
{
"id": "name",
"label": "Full name",
"kind": "short_text",
"required": true,
"autocomplete": "name"
},
{
"id": "phone",
"label": "Mobile number",
"kind": "phone",
"required": true,
"helpText": "The driver will text you before arriving."
},
{
"id": "email",
"label": "Email",
"kind": "email",
"required": false
},
{
"id": "floor",
"label": "Floor number",
"kind": "number",
"required": true,
"min": 0,
"max": 60,
"placeholder": "0"
},
{
"id": "window",
"label": "Delivery window",
"kind": "select",
"required": true,
"options": [
{
"id": "am",
"label": "Morning (8-12)"
},
{
"id": "pm",
"label": "Afternoon (12-5)"
},
{
"id": "eve",
"label": "Evening (5-8)"
}
]
},
{
"id": "notes",
"label": "Anything the driver should know?",
"kind": "long_text",
"required": false,
"maxLength": 300,
"placeholder": "Gate code, pets, parking..."
},
{
"id": "adult",
"label": "Someone over 18 will be home",
"kind": "toggle",
"required": true
},
{
"id": "updates",
"label": "Text me delivery updates",
"kind": "toggle",
"required": false
}
]
}
}Result
When the person submits, the result has these fields:
| Field | Description |
|---|---|
values | object, required. Answers keyed by field id. Optional fields left empty are omitted; toggles are always present. |
For the full example above, a result could be:
{
"values": {
"name": "Ada Lovelace",
"phone": "+1 415 555 0132",
"email": "ada@example.com",
"floor": 3,
"window": "am",
"notes": "Leave it with the doorman",
"adult": true,
"updates": false
}
}Send it
eve, AI SDK and MCP
With @pagesystems/eve, @pagesystems/ai-sdk or the MCP server, the model calls get_page_type_schema with "form" (the same JSON as GET /v1/page-types/form), then create_page with this input (no version or branding; branding comes from your dashboard):
{
"title": "Delivery details",
"description": "I need these to schedule your couch delivery.",
"cover": {
"url": "https://images.example.test/couch.jpg",
"alt": "A green three-seat couch in a bright living room"
},
"page": {
"type": "form",
"fields": [
{
"id": "name",
"label": "Full name",
"kind": "short_text",
"required": true,
"autocomplete": "name"
},
{
"id": "phone",
"label": "Mobile number",
"kind": "phone",
"required": true,
"helpText": "The driver will text you before arriving."
},
{
"id": "email",
"label": "Email",
"kind": "email",
"required": false
},
{
"id": "floor",
"label": "Floor number",
"kind": "number",
"required": true,
"min": 0,
"max": 60,
"placeholder": "0"
},
{
"id": "window",
"label": "Delivery window",
"kind": "select",
"required": true,
"options": [
{
"id": "am",
"label": "Morning (8-12)"
},
{
"id": "pm",
"label": "Afternoon (12-5)"
},
{
"id": "eve",
"label": "Evening (5-8)"
}
]
},
{
"id": "notes",
"label": "Anything the driver should know?",
"kind": "long_text",
"required": false,
"maxLength": 300,
"placeholder": "Gate code, pets, parking..."
},
{
"id": "adult",
"label": "Someone over 18 will be home",
"kind": "toggle",
"required": true
},
{
"id": "updates",
"label": "Text me delivery updates",
"kind": "toggle",
"required": false
}
]
}
}REST
curl https://pagesystems.ai/v1/pages \
-H "Authorization: Bearer $PAGE_SYSTEMS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"spec":{"version":1,"title":"Delivery details","description":"I need these to schedule your couch delivery.","branding":{"name":"Juniper Concierge","accentColor":"#0050d8"},"cover":{"url":"https://images.example.test/couch.jpg","alt":"A green three-seat couch in a bright living room"},"page":{"type":"form","fields":[{"id":"name","label":"Full name","kind":"short_text","required":true,"autocomplete":"name"},{"id":"phone","label":"Mobile number","kind":"phone","required":true,"helpText":"The driver will text you before arriving."},{"id":"email","label":"Email","kind":"email","required":false},{"id":"floor","label":"Floor number","kind":"number","required":true,"min":0,"max":60,"placeholder":"0"},{"id":"window","label":"Delivery window","kind":"select","required":true,"options":[{"id":"am","label":"Morning (8-12)"},{"id":"pm","label":"Afternoon (12-5)"},{"id":"eve","label":"Evening (5-8)"}]},{"id":"notes","label":"Anything the driver should know?","kind":"long_text","required":false,"maxLength":300,"placeholder":"Gate code, pets, parking..."},{"id":"adult","label":"Someone over 18 will be home","kind":"toggle","required":true},{"id":"updates","label":"Text me delivery updates","kind":"toggle","required":false}]}}}'Validation errors
A spec that does not validate is refused with 400 invalid_spec. The message names each problem and its path, so a model can fix the spec and try again. For example:
Leaving out the required field fields:
✖ Invalid input: expected array, received undefined
→ at page.fieldsMisspelling a field name (unknown keys are rejected):
✖ Unrecognized key: "colour"
→ at page