# Create a Form

> POST /v1/kb/forms · Knowledge base API

Source: https://developer.kustomer.com/api/knowledge-base/create-a-form

```http
POST https://{orgname}.api.kustomerapp.com/v1/kb/forms
Authorization: Bearer <API key>
```

## Authorization

Requires any one of these roles: `org.permission.kb.create`; or legacy `org.admin.content.kb.write`, `org.admin.kb.write`. The API key or user must hold at least one of them. Legacy roles predate permission sets and still work.

Creates a new form document.

## About Knowledge base forms

A form is a contact form on your help center, such as "Contact support" or "Report a problem". When a customer submits it, Kustomer opens a conversation with their answers, or hands the submission to a form hook for a workflow to process.

Forms let customers reach your team from the help center without email or chat. Admins build them in the knowledge base settings, and the help center displays them to customers. A form is a draft until you set `published: true`. Only published forms appear in the help center, and only published forms accept submissions. A form can belong to one knowledge base through `knowledgeBase`. Its `hash` and `slug` identify it in help center URLs, and Kustomer generates both if you don't send them.

#### What a submission creates

By default, a submission creates a conversation. `klass` sets the type of record it creates, and `conversation` is the only value. `channel` sets the conversation's channel, and `email` is the only value. Other settings change how a submission is handled:

| Field | Effect |
| --- | --- |
| `recaptcha` | Shows a reCAPTCHA check before the form can be sent. On by default. |
| `deflection` | Suggests related articles while the customer fills in the form, so they can find an answer without submitting. |
| `formHookEnabled` | Sends submissions to a [form hook](https://developer.kustomer.com/docs/events/inbound-webhooks) instead of creating the conversation directly, so a workflow decides what to do with them. |

#### Layout and fields

The editor saves a form's fields in `layoutV2`, which holds the rows and their order, and `componentsV2`, which is a map of field definitions keyed by component ID. A field that collects data names the attribute it fills, such as a standard or custom conversation or customer attribute (`attributeProperty`, `attributeType`, `isCustomAttribute`). `layout` and `components` are the older format of the same data. An `advanced` form is a live-code form, written as code in `body` instead of in the editor. Live-code forms can't use the built-in submission that creates a conversation.

Labels and help text can contain [snippets](https://developer.kustomer.com/api/settings-and-configurations#snippets), Kustomer's translatable text, so one form works in every help center language. List the snippet keys the form uses in `snippets`. Each key must already exist, or Kustomer rejects the form. Add `include=snippets` to a read to get their values.

#### Which endpoint to use

| To | Call |
| --- | --- |
| List forms, or read one | [`GET /v1/kb/forms`](https://developer.kustomer.com/api/knowledge-base/get-forms) or [`GET /v1/kb/forms/{id}`](https://developer.kustomer.com/api/knowledge-base/get-form-by-id) |
| Create a form | [`POST /v1/kb/forms`](https://developer.kustomer.com/api/knowledge-base/create-a-form) |
| Edit, publish or unpublish a form | [`PUT /v1/kb/forms/{id}`](https://developer.kustomer.com/api/knowledge-base/update-form) |
| Delete a form | [`DELETE /v1/kb/forms/{id}`](https://developer.kustomer.com/api/knowledge-base/delete-form) |
| Render a form in a help center or your own site, without an API key | [`GET /p/v3/kb/forms/{id}`](https://developer.kustomer.com/api/knowledge-base/get-form-by-id-public) |

The public endpoint returns a published form, with snippet text localized to `lang`. Add `preview=true` to load the unpublished version instead, for example to preview changes. Like the other public knowledge base endpoints, it needs no API key and works out the org from the host, so call your org host. Reads need a knowledge base read role, and writes need `org.admin.kb.write` or the matching permission.

Related: [Inbound webhooks](https://developer.kustomer.com/docs/events/inbound-webhooks), [Snippets](https://developer.kustomer.com/api/settings-and-configurations#snippets), [Knowledge bases](https://developer.kustomer.com/api/knowledge-base#knowledge-bases), [Knowledge base themes](https://developer.kustomer.com/api/knowledge-base#themes)

### Recipes

#### 1. Copy a contact form and publish it after a preview

Start a new form, such as "Report a problem", from an existing one, check how it renders, then make it live.

**Step 1.** [`GET /v1/kb/forms/{id}`](https://developer.kustomer.com/api/knowledge-base/get-form-by-id.md). Read the form to copy. Its fields are in `layoutV2` and `componentsV2`, and `snippets` lists the snippet keys its text uses.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/kb/forms/$FORM_ID" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" > form.json
```

```bash title="kusty"
kusty api GET /v1/kb/forms/$FORM_ID > form.json
```

**Step 2.** [`POST /v1/kb/forms`](https://developer.kustomer.com/api/knowledge-base/create-a-form.md). Create the copy with a new `name` and `published: false`, so customers don't see it yet. Kustomer generates a new `hash` and builds `slug` from the name. Drop empty values: `klass` and `channel` must be sent together or not at all, and a form with neither sends its submissions to a form hook.

```bash title="cURL"
curl --request POST "https://api.kustomerapp.com/v1/kb/forms" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data "$(jq '.data | (.attributes | {layoutV2, componentsV2, snippets, recaptcha, deflection, klass, channel, formHookEnabled})
    + {knowledgeBase: .relationships.knowledgeBase.data.id, name: "Report a problem", published: false}
    | with_entries(select(.value != null))' form.json)"
```

```bash title="kusty"
kusty api POST /v1/kb/forms --fields id --data "$(jq '.data
  | (.attributes | {layoutV2, componentsV2, snippets, recaptcha, deflection, klass, channel, formHookEnabled})
    + {knowledgeBase: .relationships.knowledgeBase.data.id, name: "Report a problem", published: false}
  | with_entries(select(.value != null))' form.json)"
```

**Step 3.** [`GET /p/v3/kb/forms/{id}`](https://developer.kustomer.com/api/knowledge-base/get-form-by-id-public.md). Load the draft the way the help center does, with `preview=true` and the language to check. No API key is needed; call your org host, which serves the default knowledge base. Snippet text comes back localized to `lang`. Without `preview=true` the endpoint only returns published forms.

```bash title="cURL"
curl "https://$ORG.api.kustomerapp.com/p/v3/kb/forms/$NEW_FORM_ID?preview=true&lang=fr&include=snippets"
```

**Step 4.** [`PUT /v1/kb/forms/{id}`](https://developer.kustomer.com/api/knowledge-base/update-form.md). When it looks right, publish it, and send `snippets` again: a `PUT` without `snippets` empties the form's list, and its translated text stops resolving. Kustomer sets `publishedAt`, and the form starts accepting submissions.

```bash title="cURL"
curl --request PUT "https://api.kustomerapp.com/v1/kb/forms/$NEW_FORM_ID" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data "$(jq '{published: true, snippets: .data.attributes.snippets}' form.json)"
```

```bash title="kusty"
kusty api PUT /v1/kb/forms/$NEW_FORM_ID \
  --data "$(jq '{published: true, snippets: .data.attributes.snippets}' form.json)"
```

> If the new form needs new text, create the [snippets](https://developer.kustomer.com/api/settings-and-configurations#snippets) first and add their keys to `snippets`; Kustomer rejects a form that lists a key that doesn't exist.

#### 2. Turn on spam protection for every live form

Find published forms that don't show a reCAPTCHA check and turn it on, for example after a wave of spam submissions.

**Step 1.** [`GET /v1/kb/forms`](https://developer.kustomer.com/api/knowledge-base/get-forms.md). Page through the forms and keep the published ones with `recaptcha: false`, with their `snippets`.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/kb/forms?page=1&pageSize=100" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
kusty api GET /v1/kb/forms --all \
  | jq -c 'select(.data.attributes.published and (.data.attributes.recaptcha | not))
      | {id: .data.id, snippets: .data.attributes.snippets}' > unprotected.jsonl
```

**Step 2.** [`PUT /v1/kb/forms/{id}`](https://developer.kustomer.com/api/knowledge-base/update-form.md). Send `recaptcha: true` to each one, with the form's `snippets` unchanged. Most fields you leave out stay as they are, but a `PUT` without `snippets` empties that list. If someone saves the form at the same moment, the update fails with `409 Conflict`; read the form again and retry.

```bash title="cURL"
curl --request PUT "https://api.kustomerapp.com/v1/kb/forms/$FORM_ID" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"recaptcha\": true, \"snippets\": $SNIPPETS_JSON}"
```

```bash title="kusty"
while read -r f; do
  kusty api PUT /v1/kb/forms/$(echo "$f" | jq -r .id) \
    --data "$(echo "$f" | jq -c '{recaptcha: true, snippets}')" --fields id,attributes.recaptcha
done < unprotected.jsonl
```

> Forms that send submissions to a [form hook](https://developer.kustomer.com/api/apps-platform#hooks-form-hooks) (`formHookEnabled: true`) can also be posted to directly, so validate the data in the workflow that handles them too.

### Example object

A real record from [`GET /v1/kb/forms/{id}`](https://developer.kustomer.com/api/knowledge-base/get-form-by-id.md):

```json
{
  "type": "form",
  "id": "6026b080fcca5f03e5541d1e",
  "attributes": {
    "name": "Contact Us",
    "slug": "contact-us",
    "hash": "ry_YeNNZ_",
    "layout": [],
    "layoutV2": [],
    "componentsV2": {},
    "advanced": false,
    "createdAt": "2021-02-12T16:44:48.361Z",
    "updatedAt": "2021-02-12T16:44:48.365Z",
    "publishedAt": "2021-02-12T16:44:48.361Z",
    "published": true,
    "snippets": [],
    "recaptcha": false,
    "klass": "conversation",
    "channel": "email",
    "deflection": false,
    "formHookEnabled": false,
    "replyFrom": "test@email.com"
  },
  "relationships": {
    "org": {
      "links": {
        "self": "/v1/orgs/5e15fa33623f88001a8e0bb5"
      },
      "data": {
        "type": "org",
        "id": "5e15fa33623f88001a8e0bb5"
      }
    },
    "knowledgeBase": {
      "links": {
        "self": "/v2/kb/knowledge-bases/5e15fa56a07888001927f8e5"
      },
      "data": {
        "type": "knowledge_base",
        "id": "5e15fa56a07888001927f8e5"
      }
    },
    "createdBy": {
      "links": {
        "self": "/v1/users/5e15fa45623f88001a8e0bbb"
      },
      "data": {
        "type": "user",
        "id": "5e15fa45623f88001a8e0bbb"
      }
    },
    "publishedBy": {
      "links": {
        "self": "/v1/users/5e15fa45623f88001a8e0bbb"
      },
      "data": {
        "type": "user",
        "id": "5e15fa45623f88001a8e0bbb"
      }
    }
  },
  "links": {
    "self": "/v1/kb/forms/6026b080fcca5f03e5541d1e"
  }
}
```

Fields:

- `attributes.name` (string): The form's name in the knowledge base settings, at most 256 characters. Kustomer builds `slug` from it if you don't send one.
- `attributes.slug` (string): The readable part of the form's help center URL.
- `attributes.hash` (string): The short ID that identifies the form in help center URLs. Kustomer generates it unless you send one, and it must be unique in the org.
- `attributes.layout` (array): The older format of `layoutV2`. When you send `layout` and `components`, Kustomer builds `layoutV2` and `componentsV2` from them.
- `attributes.layoutV2` (array): The form's rows and the order of the fields in them. Each field refers to a component in `componentsV2` by ID.
- `attributes.componentsV2` (object): The field definitions, keyed by component ID. A field that collects data names the attribute it fills (`attributeProperty`, `attributeType`, `isCustomAttribute`).
- `attributes.advanced` (boolean): `true` for a live-code form, written as code in `body` instead of in the editor. Live-code forms can't use the built-in submission that creates a conversation.
- `attributes.publishedAt` (string): When the form was last published. Kustomer sets it when you send `published` as `true`, and removes it when you unpublish the form.
- `attributes.published` (boolean): Only published forms appear in the help center and accept submissions. New forms are unpublished unless you send `published` as `true`.
- `attributes.snippets` (array): The keys of the [snippets](https://developer.kustomer.com/api/settings-and-configurations#snippets) used in the form's labels and help text. Each key must exist, or Kustomer rejects the form. A `PUT` without `snippets` empties the list, so send it back on every update. Add `include=snippets` to a read to get their values.
- `attributes.recaptcha` (boolean): Whether the form shows a reCAPTCHA check before it can be sent. On by default.
- `attributes.klass` (string): The type of record a submission creates. `conversation` is the only value. Send it together with `channel`; a form with neither sends its submissions to a form hook.
- `attributes.channel` (string): The channel of the conversation a submission creates. `email` is the only value.
- `attributes.deflection` (boolean): Whether the form suggests related articles while the customer fills it in, so they can find an answer without submitting.
- `attributes.formHookEnabled` (boolean): `true` sends submissions to a [form hook](https://developer.kustomer.com/docs/events/inbound-webhooks) for a workflow to handle, instead of creating a conversation directly.
- `relationships.knowledgeBase` (object): The knowledge base the form belongs to. Send a knowledge base ID as `knowledgeBase` to set it; it must exist.

### Relationships

- Points to **Knowledge bases** via `relationships.knowledgeBase`: The knowledge base a contact form belongs to. A form can belong to one knowledge base, and a knowledge base can have many forms.

### Permissions

Fewest roles for a read-only integration: `org.permission.kb.read`. For every endpoint: `org.permission.kb.read`, `org.permission.kb.create`, `org.permission.kb.delete`, `org.permission.kb.update`.

## Request body

Content type: `application/json` (required)

- `name` (string, required): (max length 256)
- `slug` (string): (max length 140)
- `hash` (string): (max length 140)
- `body` (string)
- `published` (boolean)
- `snippets` (array<string>)
- `layout` (array<any>)
- `components` (object<form-component>)
- `layoutV2` (array<any>)
- `componentsV2` (object<form-components-v2>)
- `knowledgeBase` (string<object-id>)
- `advanced` (boolean)
- `recaptcha` (boolean)
- `klass` (string): One of: `conversation`.
- `channel` (string): One of: `email`.
- `deflection` (boolean)
- `formHookEnabled` (boolean)
- `replyFrom` (string<email-relaxed>)

Example:

```json
{
  "name": "example_name",
  "slug": "string",
  "hash": "string",
  "body": "Example text",
  "published": true,
  "channel": "email"
}
```

## Responses

### 201

Created

- `data` (object, required)
  - `type` (string, required)
  - `id` (string, required)
  - `attributes` (object, required)
    - `name` (string, required)
    - `slug` (string, required)
    - `hash` (string, required)
    - `layout` (array<any>, required)
    - `layoutV2` (array<any>, required)
    - `componentsV2` (object, required)
    - `advanced` (boolean, required)
    - `createdAt` (string, required)
    - `updatedAt` (string, required)
    - `publishedAt` (string, required)
    - `published` (boolean, required)
    - `snippets` (array<any>, required)
    - `recaptcha` (boolean, required)
    - `klass` (string, required)
    - `channel` (string, required)
    - `deflection` (boolean, required)
    - `formHookEnabled` (boolean, required)
    - `replyFrom` (string, required)
  - `relationships` (object, required)
    - `org` (object, required)
    - `knowledgeBase` (object, required)
    - `createdBy` (object, required)
    - `publishedBy` (object, required)
  - `links` (object, required)
    - `self` (string, required)

```json
{
  "data": {
    "type": "form",
    "id": "6026b080fcca5f03e5541d1e",
    "attributes": {
      "name": "Contact Us",
      "slug": "contact-us",
      "hash": "ry_YeNNZ_",
      "layout": [],
      "layoutV2": [],
      "componentsV2": {},
      "advanced": false,
      "createdAt": "2021-02-12T16:44:48.361Z",
      "updatedAt": "2021-02-12T16:44:48.365Z",
      "publishedAt": "2021-02-12T16:44:48.361Z",
      "published": true,
      "snippets": [],
      "recaptcha": false,
      "klass": "conversation",
      "channel": "email",
      "deflection": false,
      "formHookEnabled": false,
      "replyFrom": "test@email.com"
    },
    "relationships": {
      "org": {
        "links": {
          "self": "/v1/orgs/5e15fa33623f88001a8e0bb5"
        },
        "data": {
          "type": "org",
          "id": "5e15fa33623f88001a8e0bb5"
        }
      },
      "knowledgeBase": {
        "links": {
          "self": "/v2/kb/knowledge-bases/5e15fa56a07888001927f8e5"
        },
        "data": {
          "type": "knowledge_base",
          "id": "5e15fa56a07888001927f8e5"
        }
      },
      "createdBy": {
        "links": {
          "self": "/v1/users/5e15fa45623f88001a8e0bbb"
        },
        "data": {
          "type": "user",
          "id": "5e15fa45623f88001a8e0bbb"
        }
      },
      "publishedBy": {
        "links": {
          "self": "/v1/users/5e15fa45623f88001a8e0bbb"
        },
        "data": {
          "type": "user",
          "id": "5e15fa45623f88001a8e0bbb"
        }
      }
    },
    "links": {
      "self": "/v1/kb/forms/6026b080fcca5f03e5541d1e"
    }
  }
}
```

### 400

Bad Request

### 401

Unauthorized

### 403

Forbidden

## Example request

```bash
curl --request POST \
  --url 'https://api.kustomerapp.com/v1/kb/forms' \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "example_name",
  "slug": "string",
  "hash": "string",
  "body": "Example text",
  "published": true,
  "channel": "email"
}'
```
