# Fields

> List, create, and write custom field definitions and values for contacts and companies

Source: https://docs.graph8.com/developers/fields/

Fields are the column definitions for contacts and companies. Use these endpoints to discover available fields — both built-in and custom — and to create new custom columns, populate their values, and optionally provision enrichment pipelines that fill them automatically.

---

## List Contact Fields

`GET /fields`

Returns all contact field definitions, including base fields and any custom fields. Pass `list_id` to include list-specific custom fields.

### Query Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `list_id` | integer | — | Include custom fields specific to this list |

### Example

**cURL**

```bash
curl "https://be.graph8.com/api/v1/fields" \
  -H "Authorization: Bearer $API_KEY"
```

### Response

```json
{
  "data": [
    {
      "id": 1,
      "title": "Job Level",
      "name": "job_level",
      "data_type": "text",
      "is_global": true
    }
  ]
}
```

---

## List Company Fields

`GET /fields/companies`

Returns all company field definitions, including base fields and any custom fields. Same response shape as `GET /fields`.

### Query Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `list_id` | integer | — | Include custom fields specific to this list |

### Example

**cURL**

```bash
curl "https://be.graph8.com/api/v1/fields/companies" \
  -H "Authorization: Bearer $API_KEY"
```

### Response

```json
{
  "data": [
    {
      "id": 1,
      "title": "Company Name",
      "name": "COMPANY_NAME",
      "data_type": "text",
      "is_global": true
    }
  ]
}
```

---

## Create a Custom Field

`POST /fields` → 201

Creates a single custom column for contacts or companies. Optionally provisions a waterfall enrichment pipeline alongside the column in the same request.

### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `title` | string | **R** | Display name for the column |
| `data_type` | string | O | Column data type. Default `"text"` |
| `list_id` | integer | O | Scope to a specific list. `null` or omitted = global. **Required when `enrichment` is set.** |
| `entity` | string | O | `"contacts"` or `"companies"`. Default `"contacts"` |
| `enrichment` | object | O | When present, provisions a waterfall pipeline alongside the column. See enrichment fields below. |

#### `enrichment` object fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `type` | string | O | Pipeline kind. Default `"waterfall"` (only supported kind today) |
| `name` | string | O | Pipeline display name. Default `"<title> Enrichment"` |
| `field_to_enrich` | string | O | Column slug to target. Defaults to the new column's slug |
| `skip_existing_values` | boolean | O | Skip contacts that already have a value. Default `true` |
| `providers` | array of strings | O | Shorthand provider list. Mutually exclusive with `steps`. Allowed: `graph8`, `hunter`, `prospeo`, `dropcontact`, `icypeas`, `leadmagic`, `emaillistverify`, `apollo`, `lusha`, `rocketreach` |
| `steps` | array of objects | O | Full step DTOs. Mutually exclusive with `providers` |
| `email_verification` | object | O | Post-enrichment validation config. See below. |

#### `email_verification` object fields

| Field | Type | Description |
|-------|------|-------------|
| `enabled` | boolean | Enable email verification |
| `provider` | string | `zerobounce`, `hunter`, `icypeas`, or `leadmagic` |
| `use_system_credentials` | boolean | Use graph8's system credentials for the provider |
| `accept_catchall` | boolean | Treat catch-all addresses as valid |
| `valid_statuses` | array of strings | Which statuses to accept (e.g. `["valid"]`) |

> **Note: Enrichment constraints**
> - `list_id` is **required** when `enrichment` is set — waterfall pipelines are list-scoped.
> - `providers` and `steps` are mutually exclusive. Pass one, neither, or `email_verification` alone.
> - Unknown providers return `400` (`unsupported_provider`).
> - Without `enrichment`, the column exists but has no enrichment plan — `POST /enrichment/waterfall/enrich` returns `422` (`waterfall_config_missing`) until you call `POST /enrichment/waterfall/configs`.

### Example

**cURL**

```bash
# Simple column (no enrichment)
curl -X POST "https://be.graph8.com/api/v1/fields" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Job Level",
    "data_type": "text",
    "list_id": 12345,
    "entity": "contacts"
  }'

# Column + enrichment pipeline
curl -X POST "https://be.graph8.com/api/v1/fields" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Job Level",
    "data_type": "text",
    "list_id": 12345,
    "entity": "contacts",
    "enrichment": {
      "type": "waterfall",
      "name": "Job Level Enrichment",
      "field_to_enrich": "job_level",
      "skip_existing_values": true,
      "providers": ["hunter", "prospeo"],
      "email_verification": {
        "enabled": true,
        "provider": "zerobounce",
        "use_system_credentials": true,
        "accept_catchall": false,
        "valid_statuses": ["valid"]
      }
    }
  }'
```

### Response (no enrichment)

```json
{
  "data": {
    "id": 1,
    "title": "Job Level",
    "data_type": "text",
    "name": "job_level",
    "list_id": 12345,
    "is_global": false,
    "enrichment": null
  }
}
```

### Response (with enrichment)

```json
{
  "data": {
    "id": 1,
    "title": "Job Level",
    "data_type": "text",
    "name": "job_level",
    "list_id": 12345,
    "is_global": false,
    "enrichment": {
      "column_id": "job_level",
      "config_id": "uuid",
      "name": "Job Level Enrichment",
      "type": "waterfall",
      "providers": ["hunter", "prospeo"],
      "email_verification": {
        "enabled": true,
        "provider": "zerobounce"
      }
    }
  }
}
```

> **Tip: Using `enrichment.column_id`**
> The `enrichment.column_id` in the response is the column's slug (e.g. `"job_level"`), not a `waterfall_<uuid>` handle. Pass it directly to `POST /enrichment/waterfall/enrich`.

---

## Create Multiple Custom Fields

`POST /fields/batch` → 201

Creates many custom columns in a single request. All columns are created in a single DB transaction with a **single view rebuild** — far more efficient than calling `POST /fields` in a loop when setting up a list with many columns.

`entity` and `list_id` are shared across the entire batch. `title`, `data_type`, and `enrichment` are per-column.

### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `entity` | string | O | `"contacts"` or `"companies"`. Applies to all columns. Default `"contacts"` |
| `list_id` | integer | O | Shared list scope. **Required if any field has `enrichment` set.** |
| `fields` | array of objects | **R** | 1–100 column definitions. Each item may have `title` (R), `data_type` (O), and `enrichment` (O, same shape as `POST /fields`) |

> **Caution: Batch validation and atomicity**
> - **Validation is fail-fast**: an invalid `data_type`, duplicate titles (case-insensitive) within the batch, an unsupported `enrichment.type`, `providers`+`steps` together, or a missing `list_id` when any field uses `enrichment` → **400, and nothing is created**.
> - **Column creation is atomic** — all columns commit together or none do.
> - **Per-column enrichment is best-effort**: if a column commits but its companion enrichment config fails, that column still exists and its `results[]` entry reports `ok: false` with an `error` message. The rest of the batch is unaffected.
> - Maximum **100** columns per batch.

### Example

**cURL**

```bash
curl -X POST "https://be.graph8.com/api/v1/fields/batch" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "entity": "contacts",
    "list_id": 12345,
    "fields": [
      { "title": "Persona" },
      { "title": "Account Tier", "data_type": "text" },
      {
        "title": "Work Email",
        "enrichment": {
          "type": "waterfall",
          "providers": ["graph8", "hunter", "prospeo"],
          "email_verification": {
            "enabled": true,
            "provider": "zerobounce"
          }
        }
      }
    ]
  }'
```

### Response

```json
{
  "data": {
    "entity": "contacts",
    "list_id": 12345,
    "total_requested": 3,
    "total_created": 3,
    "results": [
      {
        "title": "Persona",
        "id": 10,
        "name": "udo_persona_1700000000",
        "ok": true,
        "error": null,
        "enrichment": null
      },
      {
        "title": "Account Tier",
        "id": 11,
        "name": "udo_account_tier_1700000000",
        "ok": true,
        "error": null,
        "enrichment": null
      },
      {
        "title": "Work Email",
        "id": 12,
        "name": "udo_work_email_1700000000",
        "ok": true,
        "error": null,
        "enrichment": {
          "column_id": "udo_work_email_1700000000",
          "config_id": "uuid",
          "name": "Work Email Enrichment",
          "type": "waterfall",
          "providers": ["graph8", "hunter", "prospeo"],
          "email_verification": {
            "enabled": true,
            "provider": "zerobounce"
          }
        }
      }
    ]
  }
}
```

---

## Write a Custom Field Value

`PATCH /fields/{column_id}/values`

Sets (or clears) the value of a custom column for a single contact or company record.

### Path Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `column_id` | integer | The numeric ID of the custom field (from `GET /fields`) |

### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `record_id` | integer | **R** | Contact or company ID |
| `value` | string | O | New value. Pass `null` or omit to clear the field |
| `entity` | string | O | `"contacts"` or `"companies"`. Default `"contacts"` |

### Example

**cURL**

```bash
# Set a value
curl -X PATCH "https://be.graph8.com/api/v1/fields/1/values" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "record_id": 101,
    "value": "Director",
    "entity": "contacts"
  }'

# Clear a value
curl -X PATCH "https://be.graph8.com/api/v1/fields/1/values" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "record_id": 101,
    "value": null,
    "entity": "contacts"
  }'
```

### Response

```json
{ "data": { "column_id": 1, "record_id": 101, "updated": true } }
```

---

## Read a Custom Field Value

`GET /fields/{column_id}/values`

Reads the value of a custom column for a single contact or company record.

> **Tip: Reading many values at once**
> To pull custom values for a page of records in one call, pass `include_custom_fields=true` to `GET /contacts`, `GET /contacts/{id}`, `GET /companies`, or `GET /companies/{id}` instead of calling this endpoint per row. Each record then carries a `custom_fields` object keyed by column slug (only set values appear; unset rows return `{}`). This endpoint is best for reading a single known column on a single record.

### Path Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `column_id` | integer | The numeric ID of the custom field (from `GET /fields`) |

### Query Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `record_id` | integer | — | **R** — Contact ID (`entity=contacts`) or company ID (`entity=companies`) |
| `entity` | string | `contacts` | `contacts` or `companies` |

### Example

**cURL**

```bash
curl "https://be.graph8.com/api/v1/fields/1/values?record_id=101&entity=contacts" \
  -H "Authorization: Bearer $API_KEY"
```

### Response

```json
{
  "data": {
    "column_id": 1,
    "record_id": 101,
    "entity": "contacts",
    "name": "udo_job_level_1712000000",
    "value": "Director"
  }
}
```

`value` is `null` when the column has not been set for this record.

Errors: `400` for an invalid `entity`; `404` when the column (for that entity) or the record does not exist.
