# 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 `kind` that matches the answer (`email`, `phone`, `number`) so the right keyboard opens, and `select` with `options` when there is a fixed list.
- Write `label`s as plain questions, and use `helpText` for 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](/docs/api#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:

```json
{
  "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

```json
{
  "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:

```json
{
  "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`](/docs#quickstart-eve), [`@pagesystems/ai-sdk`](/docs#quickstart-ai-sdk) or the [MCP server](/docs#quickstart-mcp), the model calls `get_page_type_schema` with `"form"` (the same JSON as [`GET /v1/page-types/form`](/docs/api#endpoints)), then `create_page` with this input (no `version` or `branding`; branding comes from your dashboard):

```json
{
  "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

```bash
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`:**

```text
✖ Invalid input: expected array, received undefined
  → at page.fields
```

**Misspelling a field name (unknown keys are rejected):**

```text
✖ Unrecognized key: "colour"
  → at page
```
