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

# Create Contact Field

> Creates a field for your company. A key matching a seeded field shadows it for you alone; a key the platform reserves for itself is refused. A company may define at most 50 fields.

Defines a new field your contacts can carry — and your agents can speak.

## Overview

Creating a field is a **schema** change, not a data change: from this point on the key is
offered in the agent designer's variable palette, accepted by
[`PATCH /contacts/{id}`](/api-reference/contacts/update-contact), and available
as a CSV import target. Anyone who can write your contacts can define one.

```javascript theme={null}
POST /contact-fields
{
  "key": "order_number",
  "label": "Order number",
  "dataType": "string",
  "description": "The order this call is about",
  "csvAliases": ["Order #", "OrderNo"],
  "allowPiiInContext": false,
  "displayOrder": 50
}
```

## Choosing a key

The key becomes the prompt token `{{contact.<key>}}`, so it has to be something the token
grammar can express: **lowercase letters, digits and underscores, starting with a letter**, up
to 80 characters. `order_number` is fine; `Order-Number`, `2nd_order` and `order number` are
each a `400`.

<Warning>
  **A key cannot be changed later.** It is the identity of every `{{contact.<key>}}` token
  already saved inside an agent's flow *and* of every value already stored on a contact, and
  those cannot be migrated in one step — a rename would blank the tokens and orphan the values.
  [`PATCH`](/api-reference/contact-fields/update-contact-field) has no `key` property at all.
  Choose the key as deliberately as a column name.
</Warning>

Some keys are **reserved**, because the platform resolves them itself on every call:
`first_name`, `last_name`, `email`, `phone`, `company_name`, `company_description`,
`agent_name` and `profile`. Attempting one is a `400` naming it, rather than a field that is
created and then never spoken.

Every other seeded key is **yours to shadow**. `order_number`, `job_title` and `lead_source`
are platform fields, but creating a company field with the same key is legal and meaningful —
it overrides the label and type for your company alone, and the response comes back with
`isCompanyOverride: true`. So `isSystem: true` does not mean "cannot be used": it means the
platform seeded it, and only the eight keys above are actually refused.

<Tip>
  **This is the intended path for the three enum-shaped seeded fields.** `company_size`,
  `industry` and `lead_status` are seeded as `string`, because one company's lead statuses are
  not another's and platform options would be wrong for nearly everybody. Shadow the key with
  an `enum` carrying your own `enumOptions` and you get the constraint without losing the CSV
  aliases the seeded field brought with it. The `category` stays the seeded one, so the field
  does not move panels in the dashboard.
</Tip>

## Choosing a type

| `dataType` | Accepts                                                | Stored as                               |
| ---------- | ------------------------------------------------------ | --------------------------------------- |
| `string`   | any text                                               | trimmed text                            |
| `number`   | `42`, `"42"`, `"-7.5"`                                 | a number                                |
| `bool`     | `true`, `"true"`, `"yes"`, `"1"` (and their negatives) | a boolean                               |
| `date`     | anything `Date` can parse, e.g. `"2026-04-20"`         | an ISO 8601 timestamp                   |
| `enum`     | one of your own `enumOptions`                          | the matching option                     |
| `tags`     | `["vip","renewal"]`, **or** `"vip, renewal"`           | a `string[]`, trimmed and de-duplicated |

`tags` is for an **open-ended** set of labels — segments, flags, anything a person adds to as
they go. Reach for `enum` instead when the set is closed and you want a value outside it
refused. `tags` is also the one type that takes two shapes on the way in, an array or a
comma-separated string, so a single CSV cell holding several tags maps onto one field. A comma
is always a separator, so a tag cannot contain one — that holds inside an array element too. A
value that trims down to nothing clears the field, the way `""` does for text.

Bounded three ways, and the third is the one that catches people: at most **50 tags**, **60
characters** per tag, and **2000 characters** of tag text per request — so the first two are
not independent, and fifty sixty-character tags is over the aggregate. Each limit is a `422`
naming it. See
[Set Contact Fields](/api-reference/contacts/update-contact#tags-takes-two-shapes) for the
full write contract.

`enum` **requires** `enumOptions`, and the options must be unique. An enum with no options
would accept anything while claiming to be a closed set, and an import mapped against it would
pass preview and then fail row by row — so it is refused at creation instead.

Coercion is deliberately narrow. A CSV and a JSON body disagree about types for the same field,
so a string that unambiguously denotes the declared type is accepted and **nothing else is
guessed**: `"twelve"` is not a number, and `"maybe"` is not a boolean.

## Marking personal data

Set `isPii: true` for anything that identifies a person. The value is then masked in logs, and
it is **excluded** from what reaches an agent unless you also set `allowPiiInContext`.

It is not forbidden, though. Setting both is a deliberate act with a real use: for a collections
agent the amount owed is the entire reason for the call, and an agent that cannot name it is
worse than one that can. What you are choosing when you set both is to put that value in the
prompt of every call for a contact who has one — so choose it per field, and leave it off
otherwise.

## Limits

A company may define at most **50** fields of its own. Seeded fields do not count against it.

## Scopes

`write:contacts` (or its `write:candidates` alias).

<Info>
  A field with `allowPiiInContext` is summarised into the prompt of every call for a
  contact that has a value for it, so defining fields does shape what your agents say. It is
  not behind a scope of its own: the keys that can write your contacts are your own and your
  ATS partner's, and a separate grant would be one more thing to discover without narrowing
  who actually holds it.
</Info>

## Error Scenarios

* **400 Bad Request**: a malformed key; a key you have already defined; a key the platform
  reserves; an `enum` with no options or with duplicates; more than 50 fields
* **403 Forbidden**: the key lacks `write:contacts`

## Related Resources

<CardGroup cols={2}>
  <Card title="List Contact Fields" icon="list" href="/api-reference/contact-fields/list-contact-fields">
    See the catalog, seeded fields included
  </Card>

  <Card title="Update Contact Field" icon="pen" href="/api-reference/contact-fields/update-contact-field">
    Change everything except the key
  </Card>

  <Card title="Update Contact" icon="pen" href="/api-reference/contacts/update-contact">
    Store values against a contact
  </Card>

  <Card title="Delete Contact Field" icon="trash" href="/api-reference/contact-fields/delete-contact-field">
    Remove a definition, keep the values
  </Card>
</CardGroup>


## OpenAPI

````yaml POST /contact-fields
openapi: 3.0.0
info:
  title: InstaView API
  description: |-
    InstaView API Documentation

    ## Authentication

    All endpoints require API key authentication using Bearer token:
    ```
    Authorization: Bearer sk_your_api_key_here
    ```

    ## API Key Management

    The API Key module provides comprehensive key management for:
    - **Direct Client Keys**: Company-scoped keys for your applications
    - **ATS Partner Keys**: Resource-scoped keys for ATS integrations

    ### Key Features
    - HMAC-SHA256 hashing for API key storage
    - Configurable rate limiting
    - Comprehensive audit logging
    - Company-level isolation
    - Resource scoping for ATS partners
  version: 1.0.0
  contact: {}
servers:
  - url: https://api.instaview.sk
    description: Production API Gateway
security: []
tags: []
paths:
  /contact-fields:
    post:
      tags:
        - Public Contact Fields
      summary: Define a contact field (public API)
      description: >-
        Creates a field for your company. A key matching a seeded field shadows
        it for you alone; a key the platform reserves for itself is refused. A
        company may define at most 50 fields.
      operationId: PublicContactFieldsController_createContactField_v1
      parameters:
        - name: companyId
          required: false
          in: query
          description: >-
            Required for ATS API keys to specify which company to access.
            Ignored for standard company API keys.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PublicCreateContactFieldDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicContactFieldDto'
        '400':
          description: >-
            Malformed key, a key already defined, a key the platform reserves,
            or an ENUM with no options
      security:
        - bearer: []
        - bearer: []
components:
  schemas:
    PublicCreateContactFieldDto:
      type: object
      properties:
        key:
          type: string
          description: >-
            The key you will address this field by. Lowercase letters, digits
            and underscores, starting with a letter. It becomes
            `{{contact.<key>}}` in a prompt and the key in a values payload —
            and it cannot be changed afterwards.
          example: order_number
          maxLength: 80
        label:
          type: string
          description: Human label, shown wherever the field is offered for editing
          example: Order number
        dataType:
          type: string
          description: >-
            The value type. Writes to this field are validated against it.
            `tags` stores a `string[]` and accepts either an array or a
            comma-separated string, so one CSV cell maps onto one field; a value
            that trims down to no tags is read as clearing the field.
          enum:
            - string
            - number
            - date
            - bool
            - enum
            - tags
          example: string
        enumOptions:
          description: >-
            The allowed values. Required when `dataType` is `ENUM` — an enum
            with no options would accept anything while claiming to be a closed
            set — and ignored for every other type.
          example:
            - pricing page
            - webinar
            - referral
          type: array
          items:
            type: string
        isPii:
          type: boolean
          description: >-
            Mark the field as personal data. Personal data. Masked in logs, and
            EXCLUDED from everything that reaches the agent — the prompt and the
            generated contact document alike — unless `allowPiiInContext` is set
            on this field, or the platform lists it in the essential prompt set
            (`inEssentialPromptSet`).
          default: false
        csvAliases:
          description: >-
            Extra CSV header names this field should also match during an import
            Stored for the contact-field importer, which has not shipped:
            setting this changes no import behaviour today.
          example:
            - 'Order #'
            - OrderNo
          type: array
          items:
            type: string
        allowPiiInContext:
          type: boolean
          description: >-
            Let this one personal field reach the agent anyway. It joins the
            generated contact document AND the prompt, because a queryable value
            is one the agent might never think to look up, and reliability is
            the point for a field like an outstanding balance. Only meaningful
            when `isPii` is true; sending it on a field that is not personal
            data is rejected rather than ignored.
          default: false
          example: false
        description:
          type: string
          description: What the field means
          maxLength: 300
        displayOrder:
          type: number
          description: Ascending display order; ties break on key
          default: 0
      required:
        - key
        - label
        - dataType
    PublicContactFieldDto:
      type: object
      properties:
        id:
          type: string
          description: >-
            Field id. Needed only to address PATCH and DELETE; every other
            surface uses `key`.
          format: uuid
          example: 123e4567-e89b-12d3-a456-426614174000
        key:
          type: string
          description: >-
            The key you address this field by, in `{{contact.<key>}}` and in a
            values payload
          example: order_number
        label:
          type: string
          description: Human label, shown wherever the field is offered for editing
          example: Order number
        dataType:
          type: string
          description: >-
            The value type this field accepts. Writes are validated against it.
            `tags` is a `string[]` and accepts either an array or a
            comma-separated string, so one CSV cell maps onto one field.
          enum:
            - string
            - number
            - date
            - bool
            - enum
            - tags
          example: string
        category:
          type: string
          description: >-
            Which panel of the catalog this field belongs to — the grouping the
            dashboard's variable palette uses, and the first key this list is
            ordered by. Set by InstaView and not writable: a field you define is
            always `custom`, and a field of yours that shadows a seeded one
            reports the seeded field's category so the grouping is the same for
            every company.
          enum:
            - identity
            - organisation
            - commercial
            - relationship
            - scheduling
            - recruiting
            - platform
            - custom
          example: commercial
        enumOptions:
          description: The allowed values, for an ENUM field. Absent for every other type.
          example:
            - pricing page
            - webinar
            - referral
          type: array
          items:
            type: string
        isPii:
          type: boolean
          description: >-
            Personal data. Masked in logs, and EXCLUDED from everything that
            reaches the agent — the prompt and the generated contact document
            alike — unless `allowPiiInContext` is set on this field, or the
            platform lists it in the essential prompt set
            (`inEssentialPromptSet`).
          example: false
        isSystem:
          type: boolean
          description: >-
            A field the platform defines and every company shares. A seeded
            field cannot be edited or deleted directly; for most of them you can
            define your own field with the same key to override the label and
            type for your company alone. Eight keys are RESERVED and refuse even
            that (`first_name`, `last_name`, `email`, `phone`, `company_name`,
            `company_description`, `agent_name`, `profile`), because the
            platform resolves them itself.
          example: false
        csvAliases:
          description: >-
            Extra CSV header names this field also matches during an import
            Stored for the contact-field importer, which has not shipped:
            setting this changes no import behaviour today.
          example:
            - 'Order #'
            - OrderNo
          type: array
          items:
            type: string
        allowPiiInContext:
          type: boolean
          description: >-
            Let this one personal field reach the agent anyway. It joins the
            generated contact document AND the prompt, because a queryable value
            is one the agent might never think to look up, and reliability is
            the point for a field like an outstanding balance. Only meaningful
            when `isPii` is true; sending it on a field that is not personal
            data is rejected rather than ignored.
          example: false
        inEssentialPromptSet:
          type: boolean
          description: >-
            Read-only. Whether the platform puts this field in every
            context-enabled agent's prompt: a short set covering what an agent
            needs to open a call correctly. Not settable — everything else the
            contact has reaches the agent through the generated contact document
            instead, which is queried mid-call rather than paid for on every
            dial.
          example: false
        description:
          type: string
          description: What the field means
          example: The order this call is about
        displayOrder:
          type: number
          description: Ascending display order; ties break on key
          example: 50
        isCompanyOverride:
          type: boolean
          description: >-
            True when this is your own field overriding a seeded field of the
            same key
          example: false
        readOnly:
          type: boolean
          description: >-
            Catalogued so an agent author can use the token, but resolved by the
            platform on every call rather than stored per contact
            (`company_description` is your company's own blurb, not the
            contact's data). Sending a value for one is a 422. Note this is not
            the whole of what you can write — check `writable` for that.
          example: false
        writable:
          type: boolean
          description: >-
            Whether `fields` on `POST /contacts` or `PATCH /contacts/{id}`
            accepts a value for this key. The one flag to branch on before
            writing: `readOnly` alone is not enough, because a key the platform
            RESERVES (`email`, `first_name`, `phone` and the rest of the
            identity fields) reports `readOnly: false` and is still refused with
            a 422 — those are writable, but through the contact resource's own
            properties rather than as a field. False also for every read-only
            key, so a false here always means "do not send a value for this".
          example: true
      required:
        - id
        - key
        - label
        - dataType
        - category
        - isPii
        - isSystem
        - allowPiiInContext
        - inEssentialPromptSet
        - displayOrder
        - isCompanyOverride
        - readOnly
        - writable
  securitySchemes:
    bearer:
      type: http
      scheme: bearer
      description: API key for authentication using Bearer scheme

````