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

# Custom fields

> Define your project's data model: field types, behaviour flags, groups, validation of call values, the extraction schema, and safe deletion.

Each project defines its own data model. It decides which fields a call carries, what each is called and what type it holds, whether it can be filtered, sorted, grouped or searched, and how the fields are laid out on screen. Defining a field takes one request: nothing is deployed and no database schema changes.

A call has no fixed data columns. Its name, number, status, duration, cost and every business outcome you track are all custom fields. The platform's own fields, such as `status` or `duration`, are system fields of the same kind (see [the fields the platform writes](https://docs.callva.io/guides/calls.md#the-fields-the-platform-writes)). A field key is unique within its project, so two projects can each have a `status` field with entirely different options.

```bash theme={null}
export CALLVA_API="https://api.callva.io/api/v1/external"
export CALLVA_TOKEN="<your API token>"
```

> [!NOTE]
>
> Reading definitions needs `calls.read` or any field or field-group permission. Creating, updating and deleting fields need `fields.create`, `fields.update` and `fields.delete`; changing groups needs `field_groups.*`. See [Authentication](https://docs.callva.io/authentication.md).

## How fields, calls and values relate

A definition says what a field is. A value row ties one call to one definition and stores the value in the column for the field's type, so numbers compare as numbers and dates as dates. The API flattens a call's values into the call record, keyed by field key.

```mermaid theme={null}
flowchart LR
    D["Field definition<br/>appointment_confirmed<br/>boolean, filterable, extraction on"]
    C["Call<br/>id, project_id, timestamps"]
    V["Field value<br/>one per call and field<br/>stored in the boolean column"]
    R["GET /calls/{call}<br/>appointment_confirmed: true"]
    S["GET /custom-fields/extraction-schema"]
    X["Your extractor<br/>reads the transcript"]
    D --> V
    C --> V
    V --> R
    D -- "extraction_enabled" --> S
    S --> X
    X -- "PUT /calls/{call}" --> V
```

## Field types

Choose the type for what the value is. The type decides how the value is stored and validated, and whether comparisons are numeric, chronological or textual.

| Type | Use for | Filter operators |
| - | - | - |
| `text` | Names, notes, URLs, short strings (up to 65,535 characters) | `=`, `like`, `in`, `nin`, `nullneq`, `null`, `not_null`, `empty`, `not_empty` |
| `longtext` | Long free text with no length limit, such as a summary | as `text` |
| `integer` | Counts and whole numbers | `=`, `gt`, `gte`, `lt`, `lte`, `ne`, `nullneq`, `null`, `not_null`, `empty`, `not_empty` |
| `decimal` | Money, scores, measurements | as `integer` |
| `date` | A calendar day | as `integer` |
| `datetime` | A point in time, ISO 8601 | as `integer` |
| `boolean` | Flags and yes/no answers | `=`, `nullneq`, `null`, `not_null`, `empty`, `not_empty` |
| `select` | One choice from a declared option list | as `text` |
| `multiselect` | Several choices from a declared option list; the value is an array | `=`, `null`, `not_null` |
| `transcript` | The call's conversation. Written only through the [transcript endpoints](https://docs.callva.io/guides/calls.md#transcript) | not used in filters |
| `json` | A structured value kept whole | not filterable, sortable, groupable or searchable |

A `select` or `multiselect` field declares its options as `value` and `label` pairs. The value is what is stored, filtered and compared; the label is only what people see. Write and filter with the value.

## The definition

[`POST /custom-fields`](https://docs.callva.io/api-reference/custom-fields/create-a-custom-field) creates a definition from these properties.

**entity\_type** `string` *required*

What the field belongs to: `call` for call fields.

**field\_key** `string` *required*

The key the field is read, written and filtered by: lower-case letters, digits and underscores, starting with a letter or underscore, up to 100 characters. The call's own column names (`id`, `created_at`, `updated_at` and the like) are reserved.

**field\_label** `string` *required*

The name people see. Change the label freely; changing the key breaks every filter, integration and automation that names it.

**field\_type** `string` *required*

One of the types above.

**options** `object[]`

For `select` and `multiselect`: the choices, each `{"value": "...", "label": "..."}`.

**is\_required** `boolean` (default: false)

The field must be present and non-empty when a call is created, and must not be emptied when it is sent on update.

**is\_filterable** `boolean` (default: false)

The list, aggregate and trends endpoints accept filters on this field. Filtering on a field without it answers `422`.

**is\_sortable** `boolean` (default: false)

The field can be used in `sort`.

**is\_groupable** `boolean` (default: false)

The field can be used in `group` on the calls list and offered for grouping in the dashboard.

**is\_searchable** `boolean` (default: false)

The field is included in the `search` parameter's text search. Keep this narrow; every searchable field widens every search.

**is\_visible** `boolean` (default: true)

Whether the dashboard shows the field. A hidden field is still read and written through the API.

**sort\_order** `integer`

Position of the field in lists of fields and in the extraction schema.

**validation\_rules** `string[]`

Extra Laravel validation rules appended to the ones the type supplies, for example `["max:50"]`.

**help\_text** `string`

A description shown with the field.

**default\_value** `string`

Stored with the definition. The API does not fill it into calls created without the field.

**extraction\_enabled** `boolean` (default: false)

The field takes part in post-call extraction and appears in the extraction schema. Not allowed for `transcript` and `json` fields.

**extraction\_prompt** `string`

What to extract, in words an extractor reads. Required while `extraction_enabled` is true.

## Add a field and use it

**1. Create the field**

A boolean that records whether the person confirmed their appointment, filterable and sortable, and used in extraction.

```bash theme={null}
curl -X POST "$CALLVA_API/custom-fields" \
  -H "Authorization: Bearer $CALLVA_TOKEN" -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "entity_type": "call",
    "field_key": "appointment_confirmed",
    "field_label": "Appointment confirmed",
    "field_type": "boolean",
    "is_filterable": true,
    "is_sortable": true,
    "is_groupable": true,
    "help_text": "Whether the person confirmed the appointment",
    "extraction_enabled": true,
    "extraction_prompt": "Did the person confirm they will attend the appointment? true if yes, false if they declined or asked to reschedule."
  }'
```

```json Response theme={null}
{
  "success": true,
  "message": "Custom field created successfully",
  "data": {
    "id": "01a1c74f-6f8f-720f-99da-70d74ad6ca82",
    "entity_type": "call",
    "field_key": "appointment_confirmed",
    "field_label": "Appointment confirmed",
    "field_type": "boolean",
    "is_filterable": true,
    "is_sortable": true,
    "is_groupable": true,
    "is_visible": true,
    "help_text": "Whether the person confirmed the appointment",
    "extraction_enabled": true,
    "extraction_prompt": "Did the person confirm they will attend the appointment? true if yes, false if they declined or asked to reschedule.",
    "project_id": "01a1c749-5525-731a-ab9f-61ffbf9e4ef2",
    "tenant_id": "01a1c749-5387-7002-8154-5a7913629e6e",
    "created_at": "2026-11-10T21:13:54.000000Z",
    "updated_at": "2026-11-10T21:13:54.000000Z"
  }
}
```

A select field declares its options the same way:

```bash theme={null}
curl -X POST "$CALLVA_API/custom-fields" \
  -H "Authorization: Bearer $CALLVA_TOKEN" -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "entity_type": "call",
    "field_key": "callback_window",
    "field_label": "Callback window",
    "field_type": "select",
    "is_filterable": true,
    "options": [
      {"value": "morning", "label": "Morning"},
      {"value": "afternoon", "label": "Afternoon"},
      {"value": "evening", "label": "Evening"}
    ],
    "extraction_enabled": true,
    "extraction_prompt": "When did the person ask to be called back, if at all?"
  }'
```

The new field is live at once: the next request that writes or filters calls already knows it.

**2. Set it on a call**

Write values by field key with [`PUT /calls/{call}`](https://docs.callva.io/api-reference/calls/update-a-call), or include them when you create the call.

```bash theme={null}
curl -X PUT "$CALLVA_API/calls/01a1c74e-54f2-73f6-8af6-3ef541efe3a0" \
  -H "Authorization: Bearer $CALLVA_TOKEN" -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"appointment_confirmed": true, "callback_window": "morning"}'
```

```json Response theme={null}
{
  "success": true,
  "message": "Call updated successfully",
  "data": {
    "id": "01a1c74e-54f2-73f6-8af6-3ef541efe3a0",
    "...": "...",
    "name": "Alex Example",
    "status": "complete",
    "appointment_confirmed": true,
    "callback_window": "morning"
  }
}
```

**3. Filter calls by it**

Every filterable field is a query parameter of [`GET /calls`](https://docs.callva.io/api-reference/calls/list-calls).

```bash theme={null}
curl -G "$CALLVA_API/calls" \
  -H "Authorization: Bearer $CALLVA_TOKEN" -H "Accept: application/json" \
  -d appointment_confirmed=true
```

It answers the calls whose value is `true`. A select field takes a list: `callback_window_in=morning,afternoon`. The full operator list is in the [calls guide](https://docs.callva.io/guides/calls.md#list-and-filter).

**4. Check what deleting it would cost**

[`GET /custom-fields/{custom_field}/delete-impact`](https://docs.callva.io/api-reference/custom-fields/get-a-fields-deletion-impact) counts the records that hold a value for the field, without changing anything.

```bash theme={null}
curl "$CALLVA_API/custom-fields/01a1c74f-6f8f-720f-99da-70d74ad6ca82/delete-impact" \
  -H "Authorization: Bearer $CALLVA_TOKEN" -H "Accept: application/json"
```

```json Response theme={null}
{
  "success": true,
  "message": "Delete impact retrieved successfully",
  "data": { "affected_records": 2, "field_key": "appointment_confirmed", "field_label": "Appointment confirmed" }
}
```

**5. Delete it**

[`DELETE /custom-fields/{custom_field}`](https://docs.callva.io/api-reference/custom-fields/delete-a-custom-field) deletes the definition and every value stored under it.

```bash theme={null}
curl -X DELETE "$CALLVA_API/custom-fields/01a1c74f-6f8f-720f-99da-70d74ad6ca82" \
  -H "Authorization: Bearer $CALLVA_TOKEN" -H "Accept: application/json"
```

```json Response theme={null}
{
  "success": true,
  "message": "Custom field deleted successfully. 2 record(s) affected.",
  "data": { "affected_records": 2 }
}
```

> [!WARNING]
>
> Deleting a field cannot be undone: every call loses its value for it. Check the delete impact first, and export the values you want to keep.

## Validation of call values

Call values are validated against the definitions as they are at the moment of the request. The type supplies the base rule, `is_required` decides between required and optional, a select or multiselect field accepts only its option values, and any `validation_rules` are added. An update validates only the fields it carries. A value that fails answers `422` with each field named:

```bash theme={null}
curl -X PUT "$CALLVA_API/calls/01a1c74e-54f2-73f6-8af6-3ef541efe3a0" \
  -H "Authorization: Bearer $CALLVA_TOKEN" -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"appointment_confirmed": "maybe", "callback_window": "Morning"}'
```

```json Response theme={null}
{
  "success": false,
  "message": "Validation failed",
  "errors": {
    "appointment_confirmed": ["The appointment confirmed field must be true or false."],
    "callback_window": ["The selected callback window is invalid."]
  }
}
```

`"Morning"` is refused because it is the option's label; the value is `"morning"`.

> [!WARNING]
>
> A key the project does not define is ignored by `POST /calls` and `PUT /calls/{call}`: the call is saved without it. Only [`PATCH /calls/batch`](https://docs.callva.io/guides/calls#batch-update-all-or-nothing) refuses it with `422`. Check your keys against `GET /custom-fields`.

Because rules come from the current definitions, adding a required field tightens validation for every caller at once. An integration that creates calls without it starts receiving `422`.

## Read the data model

[`GET /custom-fields`](https://docs.callva.io/api-reference/custom-fields/list-custom-fields) lists the project's definitions, ordered by label. Pass `entity_type=call` (the default).

```bash theme={null}
curl "$CALLVA_API/custom-fields?entity_type=call" \
  -H "Authorization: Bearer $CALLVA_TOKEN" -H "Accept: application/json"
```

```json Response theme={null}
{
  "success": true,
  "message": "Custom field definitions retrieved successfully",
  "data": [
    {
      "id": "01a1c749-555f-73a3-abc0-e6f82539e35c",
      "entity_type": "call",
      "field_key": "duration",
      "field_label": "Call Duration",
      "field_type": "integer",
      "is_required": false,
      "is_filterable": true,
      "is_sortable": true,
      "is_groupable": false,
      "is_searchable": true,
      "is_system": true,
      "is_visible": true,
      "sort_order": 60,
      "options": null,
      "validation_rules": null,
      "default_value": null,
      "help_text": "Duration in seconds",
      "extraction_enabled": false,
      "extraction_prompt": null,
      "...": "..."
    },
    {
      "field_key": "status",
      "field_type": "select",
      "is_system": true,
      "options": [
        { "value": "scheduled", "label": "Scheduled", "color": "cyan", "is_system": true },
        { "value": "starting", "label": "Starting", "is_system": true },
        { "value": "in_progress", "label": "In Progress", "color": "yellow", "is_system": true },
        { "value": "complete", "label": "Complete", "color": "green", "is_system": true },
        { "value": "failed", "label": "Failed", "color": "indigo", "is_system": true },
        { "value": "calling", "label": "Calling", "is_system": true }
      ],
      "...": "..."
    }
  ]
}
```

Read this before writing an integration or an agent that works with calls: it is the authoritative list of keys, types, options and flags for the project.

## The extraction schema

A field with `extraction_enabled` takes part in post-call extraction: reading a finished call's transcript and filling the field from it. [`GET /custom-fields/extraction-schema`](https://docs.callva.io/api-reference/custom-fields/get-the-extraction-schema) returns those fields as one JSON Schema, ready to use as a strict structured output for a language model.

```bash theme={null}
curl "$CALLVA_API/custom-fields/extraction-schema?entity_type=call" \
  -H "Authorization: Bearer $CALLVA_TOKEN" -H "Accept: application/json"
```

```json Response theme={null}
{
  "success": true,
  "message": "Extraction schema retrieved successfully",
  "data": {
    "type": "object",
    "properties": {
      "appointment_confirmed": {
        "type": ["boolean", "null"],
        "description": "Did the person confirm they will attend the appointment? true if yes, false if they declined or asked to reschedule."
      },
      "callback_window": {
        "type": ["string", "null"],
        "enum": ["morning", "afternoon", "evening", null],
        "description": "When did the person ask to be called back, if at all?"
      }
    },
    "required": ["appointment_confirmed", "callback_window"],
    "additionalProperties": false
  }
}
```

Every enabled field is a required property keyed by its field key, in `sort_order`, described by its `extraction_prompt`, and nullable because a call may not mention it. A select field is an `enum` of its option values; a multiselect field is an array of them; dates and datetimes carry `format`. A project with no enabled fields answers an object with no properties.

The platform publishes the schema; it does not run the extraction itself. The extractor is yours: an [automation](https://docs.callva.io/guides/automations.md) or your own service reads the call's [transcript](https://docs.callva.io/guides/calls.md#transcript), asks a model for output matching the schema, and writes the result back with `PUT /calls/{call}`. Because the schema's keys are field keys and its enums are option values, the model's output is a valid update body as it stands.

```mermaid theme={null}
sequenceDiagram
    participant API as CallVA API
    participant X as Your extractor
    participant M as Language model
    X->>API: GET /custom-fields/extraction-schema
    X->>API: GET /calls/{call}/transcript
    X->>M: Transcript + schema as strict output
    M-->>X: One object with a value or null per field
    X->>API: PUT /calls/{call} with that object
```

Turning extraction on for a `transcript` or `json` field, or without a prompt, answers `422`.

## Groups and layout

Groups arrange fields for display in the dashboard; they change nothing about how a field is stored, validated or filtered. A field belongs to at most one group, and a field in no group works fully.

| Request | What it does |
| - | - |
| [`GET /custom-field-groups`](https://docs.callva.io/api-reference/custom-field-groups/list-custom-field-groups) | Groups with their fields; `include_ungrouped=true` adds the fields in no group |
| [`POST /custom-field-groups`](https://docs.callva.io/api-reference/custom-field-groups/create-a-custom-field-group) | Create a group: `entity_type`, `name`, optional `sort_order`, `show_title` |
| [`PATCH /custom-field-groups/{groupId}`](https://docs.callva.io/api-reference/custom-field-groups/update-a-custom-field-group) | Rename a group or change `show_title` |
| [`POST /custom-field-groups/reorder`](https://docs.callva.io/api-reference/custom-field-groups/reorder-custom-field-groups) | Order the groups: `{"group_ids": [...]}` |
| [`POST /custom-field-groups/{groupId}/fields`](https://docs.callva.io/api-reference/custom-field-groups/add-a-field-to-a-group) | Put a field in the group: `{"field_id": "...", "position": 0}`. A field in another group moves |
| [`POST /custom-field-groups/{groupId}/fields/reorder`](https://docs.callva.io/api-reference/custom-field-groups/reorder-a-groups-fields) | Order the fields of one group: `{"field_ids": [...]}` |
| [`PATCH /custom-field-groups/{groupId}/fields/{fieldId}`](https://docs.callva.io/api-reference/custom-field-groups/update-a-fields-group-settings) | Display settings of a field in the group: `display_mode` (`standard`, `audio_player`, `item_list`, `text_block`, `transcript`, `json`), `show_label`, `font_size` (`normal`, `large`, `extra_large`), `render_options` |
| [`DELETE /custom-field-groups/{groupId}/fields/{fieldId}`](https://docs.callva.io/api-reference/custom-field-groups/remove-a-field-from-a-group) | Take a field out of the group |
| [`DELETE /custom-field-groups/{groupId}`](https://docs.callva.io/api-reference/custom-field-groups/delete-a-custom-field-group) | Delete the group; its fields become ungrouped |

Reordering has its own endpoints so that two people arranging different parts of the layout do not overwrite each other.

```bash theme={null}
curl -X POST "$CALLVA_API/custom-field-groups" \
  -H "Authorization: Bearer $CALLVA_TOKEN" -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"entity_type": "call", "name": "Appointment", "show_title": true}'
```

```json Response theme={null}
{
  "success": true,
  "message": "Field group created successfully",
  "data": {
    "id": "01a1c74f-af52-71d1-b0c4-c040f2c5b089",
    "entity_type": "call",
    "name": "Appointment",
    "show_title": true,
    "sort_order": 3,
    "project_id": "01a1c749-5525-731a-ab9f-61ffbf9e4ef2",
    "tenant_id": "01a1c749-5387-7002-8154-5a7913629e6e",
    "created_at": "2026-11-10T21:14:10.000000Z",
    "updated_at": "2026-11-10T21:14:10.000000Z"
  }
}
```

## Changing a field

[`PUT /custom-fields/{custom_field}`](https://docs.callva.io/api-reference/custom-fields/update-a-custom-field) changes any property you send and leaves the rest.

```bash theme={null}
curl -X PUT "$CALLVA_API/custom-fields/01a1c74f-702d-708e-a523-1932e9474916" \
  -H "Authorization: Bearer $CALLVA_TOKEN" -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"field_label": "Preferred callback time"}'
```

Some changes cost more than they look:

* **Type.** Values are stored in a column per type. Changing the type of a field that already holds values leaves those values where the new type does not read them. Treat it as a data migration: create a new field, copy the values, then delete the old one.
* **Key.** Renaming `field_key` breaks every filter, integration and automation that names it. Change `field_label` instead.
* **Options.** Removing an option from a select field leaves calls that already hold that value with a value no longer in the list.

System fields (`is_system: true`) are the ones the platform depends on. Their `field_key`, `field_type` and `is_required` cannot be changed, their built-in options cannot be removed, and they cannot be deleted; each attempt answers `422`, for example `"Cannot modify 'field_type' on system field."` You can still change their label, flags, help text and add options of your own.

## Errors

| Status | When |
| - | - |
| `422` | A property fails validation (`field_key` format, unknown `field_type`, missing `extraction_prompt`, extraction on a `transcript` or `json` field), or a change to a system field that is not allowed |
| `400` | The `field_key` is reserved, or already used by another field of the same entity type in the project |
| `404` | The field id is not a field of your project |

> [!TIP]
>
> The same operations are available as MCP tools (`fields_list`, `fields_create`, `fields_update`, `fields_impact`, `fields_delete`, `fields_extraction_schema`, and `field_groups_*`) and as CLI commands (`callva fields list`, `callva fields impact <id>`, `callva fields extraction-schema`). See [MCP](https://docs.callva.io/mcp.md) and [CLI](https://docs.callva.io/cli.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.