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

# Custom field

# Custom Fields

Custom fields are the details your business keeps on each contact that Brilo does not hold by
default: an insurance carrier, a date of birth, a recall date, a preferred clinic. You define a
field once for the workspace, then every contact can have a value for it. Your agent can read
them on calls as `{{custom.<key>}}`, campaign emails can insert them, and
[Search Contacts](https://docs.brilo.ai/api-reference/endpoint/contact.md#search-contacts) can filter on them.

These endpoints do what **Settings**, then **Custom fields** does in the dashboard, so a script or
an AI agent can set fields up for you. The same tools are on the
[Brilo MCP server](https://docs.brilo.ai/api-reference/mcp/tools.md).

Errors share one shape, `{ "status", "error_code", "message", "details" }`. Branch on `error_code`;
the `message` says in plain words what to change.

## Field types

| Type | Holds | Example value |
| - | - | - |
| `text` | Short text, up to 500 characters | `"Dr Patel"` |
| `long_text` | Longer text, up to 10,000 characters | `"Prefers morning appointments, ..."` |
| `number` | A number | `54` |
| `date` | A calendar day | `"1971-03-02"` |
| `datetime` | A moment in time | `"2026-10-12T15:00:00Z"` |
| `boolean` | Yes or no | `true` |
| `select` | One of a set of choices | `"Aetna"` |
| `multi_select` | Several of a set of choices | `["recall", "vip"]` |
| `url` | A web link | `"https://example.com/portal"` |
| `email` | An email address | `"billing@example.com"` |
| `phone` | A phone number | `"+14155550101"` |

## List Custom Fields

```http theme={null}
GET /custom-fields?status=active
```

`status` is `active` (default), `archived` or `all`. Fields come in display order.

### Response

```json theme={null}
{
  "status": 200,
  "data": [
    {
      "id": "0b6f3c1a-2d4e-4f5a-8b9c-1d2e3f4a5b6c",
      "key": "insurance",
      "label": "Insurance",
      "type": "select",
      "options": ["Aetna", "Cigna", "Medicare"],
      "option_colors": { "Aetna": "blue", "Cigna": "green" },
      "required": false,
      "display_order": 0,
      "status": "active",
      "filled_by": "person",
      "description": "The patient's insurer.",
      "visible_to_agent": true,
      "memory": null,
      "created_at": "2026-10-12T10:00:00.000Z",
      "updated_at": "2026-10-12T10:00:00.000Z",
      "archived_at": null
    }
  ]
}
```

## Get a Custom Field

```http theme={null}
GET /custom-fields/{id}
```

Returns one field in the same shape. A field from another workspace returns `404`
`field_not_found`.

## Create a Custom Field

```http theme={null}
POST /custom-fields
```

| Field | Type | Required | Description |
| - | - | - | - |
| label | string | Yes | The name people see, up to 255 characters. |
| type | string | Yes | One of the types above. Cannot change later. |
| key | string | No | The key used in the API and in prompts: a lowercase letter, then lowercase letters, digits or `_`, up to 63 characters. Leave it out and one is made from the label: "Insurance plan" becomes `insurance_plan`. Cannot change later. |
| options | string\[] | For choices | The choices, required for `select` and `multi_select` and refused for other types. No repeats. |
| filled\_by | string | No | `person` (default) or `agent`, for a memory column your agent remembers from calls. |
| description | string | No | What the field means, up to 1,000 characters. People see it on the column and the contact page. For memory columns, your agent reads it to know what to listen for. |
| option\_colors | object | No | A colour for each choice, shown on its chip, for example `{"Aetna": "blue"}`: `blue`, `green`, `amber`, `pink`, `violet`, `teal`, `orange`, `gray`, `red`, `lime`, `cyan` or `brown`. Keys must be choices. Choices without one get a colour by position. |
| visible\_to\_agent | boolean | No | Whether your agent can read the field on calls, so it can greet callers by what it already knows. Default true. |
| memory | object | No | How your agent remembers it, for `filled_by` `agent`. See [Memory columns](#memory-columns). |
| accept\_price\_per\_call | number | For `agent` | The Memory price per call the person agreed to, which must be `price_per_call` from [Memory](https://docs.brilo.ai/api-reference/endpoint/memory.md). See [Memory columns](#memory-columns). |
| required | boolean | No | Whether a person must fill it in when adding a contact. Default false. |
| display\_order | integer | No | Position, lowest first. Default: after your last field. |

### Example Request

A choice field your front desk fills in:

```json theme={null}
{
  "label": "Insurance",
  "type": "select",
  "options": ["Aetna", "Cigna", "Medicare"]
}
```

A number your agent remembers after each call:

```json theme={null}
{
  "label": "Age",
  "type": "number",
  "filled_by": "agent",
  "description": "The caller's age in years, if they say it.",
  "memory": { "when_new_value": "empty_only" },
  "accept_price_per_call": 0.01
}
```

The response is `201` with the new field, shaped as in List Custom Fields.

### Keys are never reused

A key stays taken after its field is archived, because contacts keep their values. If you send a
`key` that any field uses, archived or not, the request returns `409` `key_collision` rather than
renaming it. If you leave `key` out, Brilo picks a free one by adding `_2`, `_3` and so on.

| error\_code | Status | When |
| - | - | - |
| `validation_failed` | 400 | A missing label, an unknown type, a badly formed key, a repeated choice |
| `key_reserved` | 400 | The key is a built-in contact field, such as `phone` or `email` |
| `options_required` | 400 | A `select` or `multi_select` field with no choices |
| `options_not_allowed` | 400 | Choices sent for any other type |
| `key_collision` | 409 | The key belongs to another field, including an archived one |
| `memory_field_limit` | 409 | You already have 25 memory columns |
| `memory_unavailable` | 409 | A memory column, on a plan without Memory |
| `price_not_accepted` | 409 | A memory column without `accept_price_per_call`, or with a price that is not the current one. `details` has `price_per_call`, `calls_last_30d` and `estimated_monthly` to show the person |
| `members_only` | 403 | Only a member of the workspace or an API key a person created can start Memory on a field, since it adds a charge per call. A paired assistant's key cannot |

## Change a Custom Field

```http theme={null}
PATCH /custom-fields/{id}
```

Send only what changes: `label`, `options`, `option_colors`, `filled_by`, `description`,
`visible_to_agent`, `memory`, `required` or `display_order`. Renaming changes the label for
everyone in the workspace. `options` replaces the whole list of choices; contacts keep values that
are no longer a choice. When you rename a choice, send `option_colors` too so its colour moves
with it; a colour for a choice that is gone is dropped. Send an empty `description` to clear it.
`memory` changes only the settings you send; `null` puts them back to the defaults.
Switching `filled_by` to `agent` needs `accept_price_per_call`, just like creating one. Switching
it to `person` stops the charge for that field; anyone can do it.

A field keeps its `key` and `type` for life: sending either returns `400` `immutable_field`. To
change a type, archive the field and create a new one. An archived field cannot be changed
(`400` `archived_key`).

## Memory columns

Set `filled_by` to `agent` and the field becomes a memory column: your agent remembers what the caller says in it, after each
call. The `description` tells it what to listen for. A workspace can have up to 25 of these.

Memory costs \$0.01 for each call your agent remembers from, for all memory columns together, so a person approves the price every time
a column starts remembering. Get `price_per_call` and `estimated_monthly` from
[Memory](https://docs.brilo.ai/api-reference/endpoint/memory.md), show them to the person, and once they agree send the
price as `accept_price_per_call` with `filled_by` `agent`. Without it, or with a different price,
the request returns `409` `price_not_accepted` with the price and the estimate in `details`. Only
any member of the workspace or an API key a person created (not a paired assistant's key) can do this;
anyone can change the other settings of a memory column, or switch it back to `person`.

The field then shows the approval in `memory`, read-only:

```json theme={null}
"memory": {
  "when_new_value": "keep_people_edits",
  "source_action_ids": [],
  "agents": "all",
  "accepted_price_per_call": 0.01,
  "accepted_at": "2026-10-15T10:00:00.000Z",
  "accepted_by": "9f2c4e1a-7b3d-4a5e-8c6f-1d2e3f4a5b6c"
}
```

If your plan's price changes, the field pauses until someone accepts the new one: send
`accept_price_per_call` on the field, or accept it for every paused column at once with
`POST /memory/accept-price`.

`memory` says when your agent may change a value:

| when\_new\_value | What happens when a caller says something new | Good for |
| - | - | - |
| `empty_only` | Fills it once and never changes it | Date of birth |
| `keep_people_edits` (default) | Keeps it current, but never changes what a person or another app entered | Preferred time |
| `always` | Always uses what the caller said last, even over values from your CRM | Current reason for calling |
| `append` | Adds new details to what is there (`long_text` only) | Allergies, notes |

`memory.agents` says whose calls fill the field: `"all"` (the default, every agent, including ones
you add later) or a list of agent ids from [Memory](https://docs.brilo.ai/api-reference/endpoint/memory.md), so only calls
those agents answered fill it. An empty list is refused; send `"all"` instead. Agents you add must
be in your workspace. If every listed agent is later deleted, the field pauses (`agents_deleted`)
instead of switching to every agent.

`memory.source_action_ids` links the field to post-call extractions your agents already run,
so their answer fills it. Get them from Suggested Fields.

Each value your agent remembers shows where it came from: the contact's
[record](https://docs.brilo.ai/api-reference/endpoint/contact.md) has `field_sources` with the call and the caller's own
words, and its activity shows the same on each change. In HIPAA workspaces the words are never returned, only the call.

## Suggested Fields

```http theme={null}
GET /custom-fields/suggestions
```

What your agents already pull out of calls, ready to become fields. Names a field already uses
are left out.

```json theme={null}
{
  "status": 200,
  "data": [
    {
      "name": "Insurance provider",
      "type": "select",
      "options": ["Aetna", "Cigna", "Delta Dental"],
      "description": "The caller's insurance company, if they name it.",
      "source_action_ids": ["5d1c7a0e-8f3b-4c2a-9d6e-2b7f1e4a9c30"]
    }
  ]
}
```

Create one by sending `name` as the `label`, its `type`, `options` and `description`, with
`filled_by` `agent`, `memory.source_action_ids` and `accept_price_per_call`.

## Memory Status

```http theme={null}
GET /custom-fields/{id}/memory-status
```

Whether your agent is remembering a memory column, and how often. The numbers can be up to 5 minutes old.

```json theme={null}
{
  "status": 200,
  "data": {
    "field_id": "0b6f3c1a-2d4e-4f5a-8b9c-1d2e3f4a5b6c",
    "key": "insurance",
    "state": "remembering",
    "reason": null,
    "last_filled_at": "2026-10-14T15:05:00.000Z",
    "contacts_filled_30d": 212
  }
}
```

| state | Means |
| - | - |
| `off` | A person fills this field |
| `paused` | Your agent cannot remember it now. `reason` `price_changed`: the price it was approved at is not your plan's current price. `unavailable`: Memory isn't part of your plan. `agents_deleted`: every agent it listened to was deleted. `overage_off`: overage is off for the workspace |
| `waiting` | On, but nothing remembered in the last 30 days |
| `remembering` | On, and your agent filled it in the last 30 days |

## Archive a Custom Field

```http theme={null}
DELETE /custom-fields/{id}
```

Archiving hides the field everywhere. Nothing is deleted: contacts keep their values and the key
stays taken. Archiving an archived field changes nothing.

```json theme={null}
{
  "status": 200,
  "data": { "id": "0b6f3c1a-2d4e-4f5a-8b9c-1d2e3f4a5b6c", "key": "insurance", "status": "archived", "archived_at": "2026-10-12T11:00:00.000Z" },
  "value_count": 412
}
```

`value_count` is how many contacts still hold a value for the field.

## Related

* [Contacts](https://docs.brilo.ai/api-reference/endpoint/contact.md) to set values and search on them
* [Lists](https://docs.brilo.ai/api-reference/endpoint/list.md) to group contacts
* [Memory](https://docs.brilo.ai/api-reference/endpoint/memory.md) for the price and which memory columns are paused
* [Add your own contact fields](https://docs.brilo.ai/contacts/add-your-own-contact-fields.md) for choosing good fields


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