## Validate a form

`POST /v1/crm-forms/validate`

`POST /v1/crm-forms/:id/validate`

Checks a CRM form configuration without saving it and returns a list of violations. The first path checks a draft of a new form, the second checks changes to the saved form with the given ID.

## Parameters

| Parameter | Type | Required | Description |
|----------|-----|:-----:|----------|
| `id` (path) | integer | no | ID of a saved form — only for the `POST /v1/crm-forms/:id/validate` path. List: [`GET /v1/crm-forms`](/docs/entities/crm-forms/list) |

## Request fields (body)

Pass either a flat body or an object in `fields`: `{"fields":{"name":"Contact form"}}`. An array in `fields` is the form field section of a flat body. Do not combine an object in `fields` with other top-level properties — this returns `400 INVALID_PARAMS`. Bitrix24 validates the form properties and values.

The body takes the same properties as form [creation](/docs/entities/crm-forms/create) and [update](/docs/entities/crm-forms/update). For a draft, pass the whole configuration. For a saved form, pass only the properties being changed, without `updatedTime`. An empty object `{}` checks the form as saved.

| Field | Type | Required | Description |
|------|-----|:-----:|----------|
| `name` | string \| null | no | Form name. A draft without `name` gets a violation on this field |
| `crm` | object | no | CRM binding. |
| `crm.entityScheme` | integer | no | Scheme of the CRM records the form creates. List: `data.entitySchemes` in [`GET /v1/crm-forms/configuration`](/docs/entities/crm-forms/configuration) |
| `active` | boolean | no | Whether the form is enabled. Bitrix24 validates whether this property is allowed |
| `languageId` | string \| null | no | Form language. List: `data.languages` in [`GET /v1/crm-forms/configuration`](/docs/entities/crm-forms/configuration) |
| `templateId` | string \| null | no | Scenario the form was created from. List: [List scenarios](/docs/entities/crm-forms/scenarios) |
| `appearance` | object | no | Appearance: `title`, `description`, button caption `buttonCaption` and other properties. |
| `fields` | array \| object | no | Form fields as an array, or a request envelope as an object |
| `fields[].name` | string | no | Field name, for example `LEAD_NAME`. List: [`GET /v1/crm-forms/entity-fields`](/docs/entities/crm-forms/entity-fields) |
| `fields[].type` | string | no | Field type. List: `data.fieldTypes` in [`GET /v1/crm-forms/configuration`](/docs/entities/crm-forms/configuration) |
| `fields[].label` | string | no | Field label in the form |
| `fields[].required` | boolean | no | Whether the field is required for submission. Pass it explicitly: without the flag, `true` is set on save |
| `fields[].multiple` | boolean | no | Whether the field accepts multiple values. Pass it explicitly: without the flag, `true` is set on save |
| `responsible` | object | no | Users responsible for the form results |
| `responsible.userIds` | array of integer | no | IDs of responsible users. List: [`GET /v1/users`](/docs/entities/users) |
| `agreements` | object | no | Agreement settings in the Bitrix24 schema |
| `submission` | object | no | Post-submission settings. |
| `callback` | object | no | Callback settings. |
| `payment` | object | no | Payment settings. |
| `booking` | object | no | Booking settings. |
| `presetFields` | array | no | Preset field values |
| `dependencies` | array | no | Dependencies between fields |
| `analytics` | object | no | Analytics settings |
| `integrations` | object | no | Integration settings |
| `spamProtection` | object | no | Spam protection settings |

The [Form contract fields](/docs/entities/crm-forms/fields) reference describes top-level metadata only. For a saved form, take nested property names and the current section values from the full [Get a form](/docs/entities/crm-forms/get) response. Sections are passed in the Bitrix24 schema; Bitrix24 decides which settings can be written.

## Examples

### curl — personal key

```bash
curl -X POST "https://vibecode.bitrix24.com/v1/crm-forms/validate" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Feedback form",
    "crm": { "entityScheme": 1 },
    "fields": [
      { "name": "LEAD_NAME", "type": "string", "label": "Name", "required": false, "multiple": false },
      { "name": "LEAD_PHONE", "type": "phone", "label": "Phone", "required": false, "multiple": false }
    ]
  }'
```

### curl — OAuth application

```bash
curl -X POST "https://vibecode.bitrix24.com/v1/crm-forms/validate" \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Feedback form",
    "crm": { "entityScheme": 1 },
    "fields": [
      { "name": "LEAD_NAME", "type": "string", "label": "Name", "required": false, "multiple": false },
      { "name": "LEAD_PHONE", "type": "phone", "label": "Phone", "required": false, "multiple": false }
    ]
  }'
```

### JavaScript — personal key

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/crm-forms/validate', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'Feedback form',
    crm: { entityScheme: 1 },
    fields: [
      { name: 'LEAD_NAME', type: 'string', label: 'Name', required: false, multiple: false },
      { name: 'LEAD_PHONE', type: 'phone', label: 'Phone', required: false, multiple: false },
    ],
  }),
})

const { data } = await res.json()
if (!data.valid) console.log('Violations:', data.violations)
```

### JavaScript — OAuth application

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/crm-forms/validate', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'Feedback form',
    crm: { entityScheme: 1 },
    fields: [
      { name: 'LEAD_NAME', type: 'string', label: 'Name', required: false, multiple: false },
      { name: 'LEAD_PHONE', type: 'phone', label: 'Phone', required: false, multiple: false },
    ],
  }),
})

const { data } = await res.json()
```

## Response fields

HTTP `200` on both paths, including when the check fails.

| Field | Type | Description |
|------|-----|----------|
| `success` | boolean | `true` — the check was performed. The check result is in `data.valid` |
| `data.valid` | boolean | `true` — no violations, `false` — the configuration needs fixing |
| `data.violations` | array | Violations. An empty array when `valid: true` |
| `data.violations[].field` | string | Path to the property with the violation, for example `name` |
| `data.violations[].message` | string | Violation text |
| `meta.redacted` | array of string | JSON paths where the current connection's credentials were replaced |

## Response example

No violations:

```json
{
  "success": true,
  "data": {
    "valid": true,
    "violations": []
  }
}
```

The check failed — the draft has no `name`:

```json
{
  "success": true,
  "data": {
    "valid": false,
    "violations": [
      {
        "field": "name",
        "message": "Name of the CRM form is required"
      }
    ]
  }
}
```

## Error response example

400 — an object in `fields` is combined with another top-level property:

```json
{
  "success": false,
  "error": {
    "code": "INVALID_PARAMS",
    "message": "Use either a flat body or {fields:{...}}"
  }
}
```

## Errors

| HTTP | Code | Description |
|------|-----|----------|
| 404 | `ENTITY_NOT_FOUND` | Bitrix24 did not find a requested entity or field descriptor. The original code is in `error.b24Code` |
| 400 | `INVALID_PARAMS` | `id` is not a positive integer, the body is not a JSON object, or a query string parameter was passed |
| 403 | `CRM_FORM_SYSTEM_IMMUTABLE` | Path with `id` only: the form is a system form; its settings cannot be changed |
| 403 | `BITRIX_INSUFFICIENT_SCOPE` | The Bitrix24 token lacks the required scope |
| 409 | `CRM_FORM_REST_NOT_ENABLED` | The CRM form methods are disabled on the Bitrix24 account |
| 403 | `BITRIX_ACCESS_DENIED` | Bitrix24 denied access: the user has no permission to change CRM forms |
| 403 | `SCOPE_DENIED` | The key lacks the `crm` scope |
| 401 | `MISSING_API_KEY` | `X-Api-Key` was not passed |
| 401 | `TOKEN_MISSING` | The key has no Bitrix24 access tokens |
| 422 | `BITRIX_ERROR` | Any other Bitrix24 refusal. The original message is kept in `error.message` |
| 502 | `BITRIX_UNAVAILABLE` | Bitrix24 is unavailable or returned a malformed response |

Bitrix24 errors include the original code in `error.b24Code` and field violations in `error.validation` when provided. `CRM_FORM_*` codes retain their HTTP status, code and message. Common 429, 502 and 503 responses: [Limits and optimization](/docs/optimization). Full list of common API errors: [Errors](/docs/errors).

## Known specifics

**Works with a read-only key.** The check saves nothing and does not change the form's `updatedTime`, so a read-only key can call it too.

## See also

- [Create a form](/docs/entities/crm-forms/create)
- [Update a form](/docs/entities/crm-forms/update)
- [Get a form](/docs/entities/crm-forms/get)
- [Form contract fields](/docs/entities/crm-forms/fields)
- [Get the builder configuration](/docs/entities/crm-forms/configuration)
- [List entity fields](/docs/entities/crm-forms/entity-fields)
- [Limits and optimization](/docs/optimization)
