> ## Documentation Index
> Fetch the complete documentation index at: https://docs.penbox.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Elements

> Every element type you can put in a form, and how to configure it

Elements are the building blocks of a form. Each step holds a list of them: input fields that collect an answer, content blocks that only display something, and a few technical elements that drive behaviour.

Every option on this page is read by the form engine.

## Anatomy of an element

An element is an object with a `type`. Everything else is optional and depends on that type.

```json theme={null}
{
  "key": "company_name",
  "type": "text",
  "title": "Company name",
  "placeholder": "Acme Corporation",
  "required": true
}
```

These properties work on every element type:

| Property   | Type               | Default      | Description                                                           |
| ---------- | ------------------ | ------------ | --------------------------------------------------------------------- |
| `type`     | `string`           | **Required** | The element type, such as `text` or `file`.                           |
| `key`      | `string`           | —            | Where the answer is stored. See [Keys](#keys).                        |
| `title`    | `string \| object` | —            | The label shown above the field. See [Titles and help text](#titles). |
| `required` | `boolean`          | `false`      | Whether an answer is mandatory. See [Required](#required).            |
| `default`  | `any`              | —            | Value used when the field has never been answered.                    |
| `value`    | `any`              | —            | Value forced onto the field, overriding stored data.                  |
| `id`       | `string`           | —            | Explicit identifier. Generated automatically when omitted.            |
| `error`    | `string \| object` | —            | Custom error message, replacing the validation message.               |
| `meta`     | `object`           | —            | Free-form data attached to the element. Ignored by the engine.        |

## How elements behave

<AccordionGroup>
  <Accordion title="Keys — where the answer is stored" id="keys" icon="key">
    The `key` decides where the answer lands in the form data.

    A key without a dot is automatically prefixed with `data.` — so `"key": "birth_date"` stores the answer in `data.birth_date`. A key that already contains a dot is used as-is, which is how you write into the contact:

    ```json theme={null}
    { "key": "user.email", "type": "text", "format": "email" }
    ```

    An element with no `key` collects nothing. That is expected for content blocks such as `paragraph` or `image`.
  </Accordion>

  <Accordion title="Titles and help text" id="titles" icon="text">
    `title` accepts a plain string, or an object carrying an extra help message shown next to the label:

    ```json theme={null}
    {
      "key": "vat_number",
      "type": "text",
      "title": {
        "text": "VAT number",
        "help": "Found on any invoice, starting with your country code."
      }
    }
    ```

    Some elements also accept a `label`, which is the text shown *inside* the control rather than above it — the text next to a `checkbox`, or the caption of a `button`. Where both exist, `title` is the field label and `label` is the control's own text.
  </Accordion>

  <Accordion title="Required — and when it is ignored" id="required" icon="asterisk">
    `required: true` only takes effect when the element can actually be answered. The engine ignores it when there is nothing to answer: a `choices` element with an empty `choices` list, a `signature` or `download` with no `items`, and always on `paragraph`, `image` and `toggle`.

    A required `checkbox` must be *checked*, not merely answered.
  </Accordion>

  <Accordion title="Showing an element conditionally" id="conditional-elements" icon="code-branch">
    To show an element only under a condition, wrap it in an `:if` / `:then` block instead of putting a property on the element:

    ```json theme={null}
    {
      ":if": { ":cmp": "{data.account_type}", ":eq": "business" },
      ":then": {
        "key": "company_registration",
        "type": "text",
        "title": "Company registration number"
      }
    }
    ```

    `:then` accepts a single element or an array of elements. The full operator syntax lives in the [penscript reference](/penscript/logic).
  </Accordion>

  <Accordion title="Auto-advance — submitting on answer" id="auto-advance" icon="forward">
    Most interactive elements accept `submit_on_change`. When set to `true`, answering the element submits the step immediately instead of waiting for the submit button. The Studio labels this option **Auto-advance**.

    <Tip>
      Auto-advance works well on a step that asks a single question — a yes/no
      choice that branches the rest of the form. On a step with several fields it
      submits before the contact is done.
    </Tip>
  </Accordion>
</AccordionGroup>

## Text and numbers

<AccordionGroup>
  <Accordion title="text — free text, email or phone" id="text" icon="text">
    Single-line or multi-line free text. `format` also turns it into an email or phone input.

    | Option         | Type      | Default | Description                                                       |
    | -------------- | --------- | ------- | ----------------------------------------------------------------- |
    | `format`       | `string`  | `text`  | `text`, `email` or `tel`. Drives the keyboard and the validation. |
    | `multiline`    | `boolean` | `false` | Renders a textarea. Only with `format: "text"`.                   |
    | `placeholder`  | `string`  | —       | Hint shown inside the empty field.                                |
    | `hint`         | `string`  | —       | Persistent hint shown under the field.                            |
    | `min`          | `number`  | `0`     | Minimum number of characters.                                     |
    | `max`          | `number`  | —       | Maximum number of characters.                                     |
    | `pattern`      | `string`  | —       | Regular expression the answer must match.                         |
    | `mask`         | `string`  | —       | Input mask. Only with `format: "text"`.                           |
    | `prefix`       | `string`  | —       | Text displayed before the value.                                  |
    | `suffix`       | `string`  | —       | Text displayed after the value.                                   |
    | `transforms`   | `array`   | —       | Transformations applied to the typed value, such as uppercasing.  |
    | `autocomplete` | `string`  | —       | Browser autocomplete hint, e.g. `given-name`, `postal-code`.      |
    | `clearable`    | `boolean` | `true`  | Shows the clear button. Set to `false` to hide it.                |
    | `readonly`     | `boolean` | `false` | Displays the value without allowing edits.                        |

    ```json theme={null}
    {
      "key": "claim_description",
      "type": "text",
      "title": "What happened?",
      "multiline": true,
      "max": 2000,
      "placeholder": "Describe the incident, when it happened, and who was involved."
    }
    ```

    <Note>
      For phone numbers, the [`phone`](#phone) element gives a better experience
      than `format: "tel"`: country prefix selector, national formatting, and
      mobile-only validation.
    </Note>
  </Accordion>

  <Accordion title="number — numeric input" id="number" icon="hashtag">
    | Option         | Type      | Default | Description                                |
    | -------------- | --------- | ------- | ------------------------------------------ |
    | `min`          | `number`  | —       | Lowest accepted value.                     |
    | `max`          | `number`  | —       | Highest accepted value.                    |
    | `decimals`     | `number`  | `0`     | Maximum number of decimals, up to 100.     |
    | `placeholder`  | `string`  | —       | Hint shown inside the empty field.         |
    | `hint`         | `string`  | —       | Persistent hint shown under the field.     |
    | `prefix`       | `string`  | —       | Text displayed before the value.           |
    | `suffix`       | `string`  | —       | Text displayed after the value, e.g. `km`. |
    | `autocomplete` | `string`  | —       | Browser autocomplete hint.                 |
    | `clearable`    | `boolean` | `true`  | Shows the clear button.                    |
    | `readonly`     | `boolean` | `false` | Displays the value without allowing edits. |

    ```json theme={null}
    {
      "key": "mileage",
      "type": "number",
      "title": "Mileage",
      "suffix": "km",
      "min": 0,
      "max": 999999
    }
    ```
  </Accordion>

  <Accordion title="currency — monetary amount" id="currency" icon="coins">
    Same shape as `number`, but the answer is always capped at two decimals and rendered with thousands separators.

    | Option        | Type      | Default | Description                              |
    | ------------- | --------- | ------- | ---------------------------------------- |
    | `min`         | `number`  | `0`     | Lowest accepted amount.                  |
    | `max`         | `number`  | —       | Highest accepted amount.                 |
    | `prefix`      | `string`  | —       | Currency symbol shown before the amount. |
    | `suffix`      | `string`  | —       | Currency symbol shown after the amount.  |
    | `placeholder` | `string`  | —       | Hint shown inside the empty field.       |
    | `hint`        | `string`  | —       | Persistent hint shown under the field.   |
    | `clearable`   | `boolean` | `true`  | Shows the clear button.                  |

    ```json theme={null}
    {
      "key": "damage_estimate",
      "type": "currency",
      "title": "Estimated damage",
      "prefix": "€"
    }
    ```
  </Accordion>

  <Accordion title="bank-account — IBAN" id="bank-account" icon="building-columns">
    The answer is validated against the IBAN checksum, stored without spaces, and displayed grouped by four characters.

    | Option        | Type     | Default | Description                            |
    | ------------- | -------- | ------- | -------------------------------------- |
    | `placeholder` | `string` | —       | Hint shown inside the empty field.     |
    | `hint`        | `string` | —       | Persistent hint shown under the field. |

    ```json theme={null}
    {
      "key": "iban",
      "type": "bank-account",
      "title": "Account to refund",
      "placeholder": "BE00 0000 0000 0000",
      "required": true
    }
    ```
  </Accordion>
</AccordionGroup>

## Dates

<AccordionGroup>
  <Accordion title="date — date picker" id="date" icon="calendar">
    The answer is stored as `YYYY-MM-DD`.

    | Option        | Type      | Default            | Description                                                                                           |
    | ------------- | --------- | ------------------ | ----------------------------------------------------------------------------------------------------- |
    | `pattern`     | `string`  | `locale-dependent` | Display format, e.g. `D/M/Y`. Defaults to `M/D/Y` in English and `D/M/Y` in French, Dutch and German. |
    | `patterns`    | `object`  | —                  | One pattern per locale, e.g. `{ "en": "M/D/Y", "fr": "D/M/Y" }`.                                      |
    | `min`         | `date`    | `1900-01-01`       | Earliest accepted date.                                                                               |
    | `max`         | `date`    | `min + 200 years`  | Latest accepted date.                                                                                 |
    | `weekDays`    | `boolean` | `false`            | Restricts the picker to weekdays.                                                                     |
    | `picker`      | `boolean` | `true`             | Set to `false` to force manual typing instead of the calendar.                                        |
    | `placeholder` | `string`  | —                  | Hint shown inside the empty field.                                                                    |
    | `hint`        | `string`  | —                  | Persistent hint shown under the field.                                                                |

    ```json theme={null}
    {
      "key": "incident_date",
      "type": "date",
      "title": "Date of the incident",
      "max": "today",
      "required": true
    }
    ```
  </Accordion>

  <Accordion title="datetime — date and time picker" id="datetime" icon="clock">
    Same options as [`date`](#date), minus `picker`. The answer is stored as an ISO 8601 timestamp.
  </Accordion>
</AccordionGroup>

## Contact details

<AccordionGroup>
  <Accordion title="phone — phone number" id="phone" icon="phone">
    Phone input with a country prefix selector. The answer is stored in international E.164 format, and displayed in national format when it matches the configured country.

    | Option         | Type      | Default | Description                                                  |
    | -------------- | --------- | ------- | ------------------------------------------------------------ |
    | `country`      | `string`  | —       | Default country, as a two-letter code, e.g. `BE`.            |
    | `accept`       | `string`  | —       | `mobile` or `fixed-line`. Rejects numbers of the other kind. |
    | `placeholder`  | `string`  | —       | Hint shown inside the empty field.                           |
    | `hint`         | `string`  | —       | Persistent hint shown under the field.                       |
    | `autocomplete` | `boolean` | `false` | Enables browser autocomplete.                                |

    ```json theme={null}
    {
      "key": "user.phone",
      "type": "phone",
      "title": "Mobile number",
      "country": "BE",
      "accept": "mobile",
      "required": true
    }
    ```

    <Tip>
      Use `accept: "mobile"` whenever you plan to send an SMS — a reminder, or an
      SMS signature. It stops a landline from being accepted at fill time instead
      of failing silently later.
    </Tip>
  </Accordion>

  <Accordion title="country — country picker" id="country" icon="globe">
    The list is built from the ISO 3166 country list and translated into the form locale (English, French, Dutch and German; any other locale falls back to English). The answer is stored as a two-letter country code.

    | Option             | Type      | Default | Description                            |
    | ------------------ | --------- | ------- | -------------------------------------- |
    | `placeholder`      | `string`  | —       | Hint shown inside the empty field.     |
    | `hint`             | `string`  | —       | Persistent hint shown under the field. |
    | `readonly`         | `boolean` | `false` | Displays the value without editing.    |
    | `submit_on_change` | `boolean` | `false` | Submits the step on selection.         |
  </Accordion>
</AccordionGroup>

## Choices and ratings

Every choice element shares the same `choices` list:

```json theme={null}
"choices": [
  { "value": "car", "label": "Car" },
  { "value": "bike", "label": "Bike" },
  { "value": "other", "label": "Something else" }
]
```

`value` can be a string, a number, a boolean or `null`. `label` is what the contact reads; when omitted, the value itself is displayed. A label can also carry help text, like a title: `{ "text": "Car", "help": "Including vans under 3.5t" }`.

<Note>
  A `null` choice — an explicit "none of these" — is only added when the element
  is not required.
</Note>

<AccordionGroup>
  <Accordion title="choices — the general-purpose selector" id="choices" icon="list-check">
    Handles both single and multiple selection, and renders either as a list or as cards.

    | Option             | Type      | Default | Description                                                               |
    | ------------------ | --------- | ------- | ------------------------------------------------------------------------- |
    | `choices`          | `array`   | `[]`    | The available options.                                                    |
    | `min`              | `number`  | —       | Minimum number of selected options.                                       |
    | `max`              | `number`  | —       | Maximum number of selected options. `max: 1` makes it a single selection. |
    | `display`          | `string`  | —       | `horizontal` or `vertical`.                                               |
    | `cards`            | `boolean` | `false` | Renders each option as a clickable card.                                  |
    | `submit_on_change` | `boolean` | `false` | Submits the step on selection.                                            |

    With `max: 1` the answer is stored as a single value; otherwise it is stored as an array.

    ```json theme={null}
    {
      "key": "vehicle_type",
      "type": "choices",
      "title": "What was damaged?",
      "choices": [
        { "value": "car", "label": "Car" },
        { "value": "bike", "label": "Bike" }
      ],
      "max": 1,
      "cards": true,
      "display": "horizontal",
      "submit_on_change": true
    }
    ```
  </Accordion>

  <Accordion title="checkboxes — multiple selection" id="checkboxes" icon="square-check">
    Multiple selection rendered as a list of checkboxes. Same options as [`choices`](#choices).
  </Accordion>

  <Accordion title="radio — single selection" id="radio" icon="circle-dot">
    Single selection rendered as radio buttons.

    | Option             | Type      | Default | Description                                  |
    | ------------------ | --------- | ------- | -------------------------------------------- |
    | `choices`          | `array`   | `[]`    | The available options.                       |
    | `allow_custom`     | `boolean` | `false` | Accepts a free-text answer outside the list. |
    | `submit_on_change` | `boolean` | `false` | Submits the step on selection.               |
  </Accordion>

  <Accordion title="toggles — single selection as buttons" id="toggles" icon="toggle-on">
    Single selection rendered as a row of toggle buttons. Same options as [`radio`](#radio).
  </Accordion>

  <Accordion title="autocomplete — searchable dropdown" id="autocomplete" icon="magnifying-glass">
    Single selection from a searchable dropdown. Use it when the list is too long for radio buttons.

    | Option             | Type      | Default | Description                             |
    | ------------------ | --------- | ------- | --------------------------------------- |
    | `choices`          | `array`   | `[]`    | The available options.                  |
    | `allow_custom`     | `boolean` | `false` | Accepts a value typed outside the list. |
    | `placeholder`      | `string`  | —       | Hint shown inside the empty field.      |
    | `hint`             | `string`  | —       | Persistent hint shown under the field.  |
    | `readonly`         | `boolean` | `false` | Displays the value without editing.     |
    | `submit_on_change` | `boolean` | `false` | Submits the step on selection.          |
  </Accordion>

  <Accordion title="checkbox — a single box to tick" id="checkbox" icon="check">
    For a consent or an acknowledgement. The answer is a boolean.

    | Option             | Type      | Default | Description                     |
    | ------------------ | --------- | ------- | ------------------------------- |
    | `label`            | `string`  | —       | The text shown next to the box. |
    | `submit_on_change` | `boolean` | `false` | Submits the step on change.     |

    ```json theme={null}
    {
      "key": "terms_accepted",
      "type": "checkbox",
      "label": "I confirm the information above is accurate",
      "required": true
    }
    ```

    <Warning>
      A required `checkbox` must be ticked to pass validation. Unticking it blocks
      the step.
    </Warning>
  </Accordion>

  <Accordion title="toggle — a single switch" id="toggle" icon="toggle-off">
    Same options as [`checkbox`](#checkbox), plus `inline` to place it on the same line as its title.

    A `toggle` can never be required — use a `checkbox` when the answer must be positive.
  </Accordion>

  <Accordion title="rating — five stars" id="rating" icon="star">
    The answer is an integer between 0 and 5. This element takes no options.
  </Accordion>
</AccordionGroup>

## Files and signatures

<AccordionGroup>
  <Accordion title="file — upload" id="file" icon="file-arrow-up">
    | Option        | Type      | Default | Description                                                                                            |
    | ------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------ |
    | `multiple`    | `boolean` | `false` | Allows several files. Changes the answer from a single file to a list.                                 |
    | `min`         | `number`  | `0`     | Minimum number of files. Only with `multiple: true`.                                                   |
    | `max`         | `number`  | —       | Maximum number of files. Only with `multiple: true`.                                                   |
    | `accept`      | `string`  | —       | Comma-separated list of accepted types, e.g. `".pdf,.jpg,image/*"`.                                    |
    | `minSize`     | `number`  | `0`     | Minimum size per file, in bytes.                                                                       |
    | `maxSize`     | `number`  | —       | Maximum size per file, in bytes.                                                                       |
    | `custom_name` | `string`  | —       | Renames uploaded files, so attachments arrive with a predictable name.                                 |
    | `extract`     | `object`  | —       | Document intelligence extraction. See [Document intelligence](/agent/templates/document-intelligence). |
    | `hint`        | `string`  | —       | Persistent hint shown under the field.                                                                 |

    ```json theme={null}
    {
      "key": "registration_certificate",
      "type": "file",
      "title": "Vehicle registration certificate",
      "accept": ".pdf,.jpg,.jpeg,.png",
      "maxSize": 10485760,
      "custom_name": "registration",
      "required": true
    }
    ```

    <Note>
      `accept` is a single comma-separated string, not an array. It takes
      extensions (`.pdf`), exact MIME types (`application/pdf`) and wildcards
      (`image/*`).
    </Note>
  </Accordion>

  <Accordion title="download — documents to download" id="download" icon="download">
    Offers one or more documents to the contact — a policy, a quote, terms and conditions. Documents can be filled with form data before being served.

    | Option             | Type      | Default      | Description                                                                                                                                          |
    | ------------------ | --------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `items`            | `array`   | **Required** | The documents to offer. Each item takes `uri`, `key`, `name`, `type`, `size`, `fill` (data merged into the document) and `fillOptions.convertToPdf`. |
    | `custom_name`      | `string`  | —            | Renames the downloaded files.                                                                                                                        |
    | `submit_on_change` | `boolean` | `false`      | Submits the step once a document has been downloaded.                                                                                                |
  </Accordion>

  <Accordion title="signature — electronic signature" id="signature" icon="pen-to-square">
    | Option               | Type      | Default | Description                                                                                                                                                                                                                                                                                                           |
    | -------------------- | --------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `items`              | `array`   | `[]`    | The documents to sign. Each item takes a `uri` pointing at the document, an optional `name`, a `fill` object merged into it, and a `signatures` array placing each signature — by page coordinates (`page`, `left`, `top`, and optionally `width`, `height`), by form field name (`field`), or by text hint (`hint`). |
    | `method`             | `string`  | `sms`   | `sms`, `email`, `handwritten`, `id-card`, `itsme` or `swisscom`.                                                                                                                                                                                                                                                      |
    | `merge`              | `boolean` | `true`  | Merges all documents into a single signed file.                                                                                                                                                                                                                                                                       |
    | `allow_rejection`    | `boolean` | `false` | Lets the contact refuse to sign without blocking the form.                                                                                                                                                                                                                                                            |
    | `additional_signers` | `array`   | `[]`    | Other people who must sign, each with `given_name`, `family_name`, `email`, and optionally `phone`, `locale` and `signature_hint`.                                                                                                                                                                                    |
    | `hide_files`         | `boolean` | `false` | Hides the document list.                                                                                                                                                                                                                                                                                              |
    | `hide_download`      | `boolean` | `false` | Hides the download button.                                                                                                                                                                                                                                                                                            |
    | `legal_notice`       | `object`  | —       | Custom legal notice shown before signing.                                                                                                                                                                                                                                                                             |
    | `name`               | `string`  | —       | Name of the resulting signed document.                                                                                                                                                                                                                                                                                |
    | `locale`             | `string`  | —       | Forces the signing interface language.                                                                                                                                                                                                                                                                                |
    | `submit_on_change`   | `boolean` | `false` | Submits the step once signed.                                                                                                                                                                                                                                                                                         |

    <Note>
      A `signature` with an empty `items` list cannot be required — there is
      nothing to sign. The same applies to [`download`](#download).
    </Note>
  </Accordion>
</AccordionGroup>

## Content blocks

These elements display something and collect no answer. They take no `key` and cannot be required.

<AccordionGroup>
  <Accordion title="paragraph — a block of text" id="paragraph" icon="align-left">
    | Option    | Type     | Default | Description                             |
    | --------- | -------- | ------- | --------------------------------------- |
    | `content` | `string` | —       | The text to display. HTML is supported. |
  </Accordion>

  <Accordion title="card — a highlighted block" id="card" icon="square">
    Optionally with an icon, a colour and action buttons.

    | Option    | Type      | Default | Description                                                                      |
    | --------- | --------- | ------- | -------------------------------------------------------------------------------- |
    | `content` | `string`  | —       | The text inside the card. HTML is supported.                                     |
    | `color`   | `string`  | —       | Background colour, e.g. `primary lighten-5`.                                     |
    | `dark`    | `boolean` | `false` | Switches the text to its light variant, for dark backgrounds.                    |
    | `icon`    | `string`  | —       | Icon shown in the card.                                                          |
    | `actions` | `array`   | —       | Buttons, each with a `title`, an optional `icon`, and either a `url` or a `key`. |
  </Accordion>

  <Accordion title="image — an image" id="image" icon="image">
    | Option    | Type             | Default  | Description                                      |
    | --------- | ---------------- | -------- | ------------------------------------------------ |
    | `src`     | `string`         | —        | Image URL. Must start with `https://` or `//`.   |
    | `alt`     | `string`         | —        | Alternative text, read by screen readers.        |
    | `width`   | `string`         | `100%`   | CSS width.                                       |
    | `height`  | `number \| auto` | `200`    | Height in pixels, between 1 and 1000, or `auto`. |
    | `justify` | `string`         | `center` | `left`, `right`, `center`, `start` or `end`.     |
  </Accordion>
</AccordionGroup>

## Technical elements

<AccordionGroup>
  <Accordion title="submit — the step button" id="submit" icon="paper-plane">
    | Option   | Type     | Default | Description                              |
    | -------- | -------- | ------- | ---------------------------------------- |
    | `label`  | `string` | —       | The button caption.                      |
    | `return` | `any`    | —       | Value stored when the step is submitted. |

    You rarely need to declare it: when a step contains no `submit` element, one is appended automatically — unless the last element of the step has `submit_on_change: true`, in which case answering it submits the step.
  </Accordion>

  <Accordion title="button — an extra button that stores a value" id="button" icon="hand-pointer">
    Use it to branch a form without a visible choice list.

    | Option             | Type      | Default | Description                              |
    | ------------------ | --------- | ------- | ---------------------------------------- |
    | `label`            | `string`  | —       | The button caption.                      |
    | `return`           | `any`     | `null`  | Value stored when the button is clicked. |
    | `outlined`         | `boolean` | `false` | Renders the button outlined.             |
    | `submit_on_change` | `boolean` | `false` | Submits the step when clicked.           |
  </Accordion>

  <Accordion title="hidden — store a value invisibly" id="hidden" icon="eye-slash">
    Typically used to carry a campaign source, an external identifier, or a computed value.

    ```json theme={null}
    {
      "key": "source",
      "type": "hidden",
      "default": "email_campaign"
    }
    ```

    A required `hidden` element blocks the form until the value is non-empty — useful to guarantee that a computed value was actually resolved.
  </Accordion>

  <Accordion title="api — call an external API mid-form" id="api" icon="globe">
    Calls an external API from inside the form and stores the response, so later steps and pricing can use it.

    | Option                 | Type      | Default      | Description                                                                                                        |
    | ---------------------- | --------- | ------------ | ------------------------------------------------------------------------------------------------------------------ |
    | `url`                  | `string`  | **Required** | The endpoint to call.                                                                                              |
    | `method`               | `string`  | `GET`        | `GET`, `POST`, `PUT`, `PATCH` or `DELETE`.                                                                         |
    | `headers`              | `object`  | `{}`         | Request headers.                                                                                                   |
    | `body`                 | `object`  | `{}`         | Request body.                                                                                                      |
    | `mapping`              | `object`  | —            | Maps the response into form data keys.                                                                             |
    | `store_response`       | `boolean` | `true`       | Stores the raw response alongside the mapped values.                                                               |
    | `validate_step`        | `boolean` | `false`      | Makes a failed call block the step.                                                                                |
    | `invalidate_on_change` | `array`   | `[]`         | Data keys to watch. When any of them changes, the stored response is treated as stale and the call must run again. |
    | `invalidate_message`   | `string`  | —            | Message shown when the response has been invalidated.                                                              |
    | `auth`                 | `object`  | —            | Authentication, e.g. `{ "method": "creditsafe", "username": "…", "password": "…" }`.                               |
    | `submit_on_change`     | `boolean` | `false`      | Submits the step once the call succeeds.                                                                           |

    ```json theme={null}
    {
      "key": "pricing",
      "type": "api",
      "url": "https://api.example.com/quote",
      "method": "POST",
      "body": { "vehicle": "{data.vehicle_type}", "value": "{data.vehicle_value}" },
      "invalidate_on_change": ["data.vehicle_type", "data.vehicle_value"],
      "invalidate_message": "Your quote is out of date, please recalculate it."
    }
    ```

    <Warning>
      Without `invalidate_on_change`, a contact who goes back and changes an input
      keeps the response computed from the old answers. List every key the call
      depends on.
    </Warning>
  </Accordion>

  <Accordion title="webflow — hand over to an external flow" id="webflow" icon="arrow-up-right-from-square">
    Sends the contact to an external web flow — an identity check, a third-party payment — and stores what comes back.

    | Option             | Type      | Default      | Description                              |
    | ------------------ | --------- | ------------ | ---------------------------------------- |
    | `href`             | `string`  | **Required** | The external URL.                        |
    | `mode`             | `string`  | `redirect`   | `redirect` or `popup`.                   |
    | `setup.payload`    | `object`  | —            | Data sent to the external flow on start. |
    | `submit_on_change` | `boolean` | `false`      | Submits the step once the flow returns.  |
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Steps and logic" icon="list-ol" href="/forms/form-templates/steps">
    Organise elements into steps and branch on answers
  </Card>

  <Card title="Portal pages" icon="browser" href="/forms/form-templates/portal-pages">
    Welcome, review and ending screens
  </Card>

  <Card title="Penscript" icon="code" href="/penscript/introduction">
    The expression language behind conditions and variables
  </Card>

  <Card title="Form templates" icon="file-lines" href="/forms/form-templates">
    Back to form templates
  </Card>
</CardGroup>
