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

# Custom columns

> Attach your own typed data — scores, owners, links, flags — to the contacts in a list

Lists store the contact fields Fuse knows about: name, title, company, and so on. Custom columns are where your data goes — a lead score from your model, an account owner, a link to the CRM record. A column is defined once per list with a type, and every contact in the list gets a cell you can write to and read back.

All three endpoints on this page require a key with the `lists` [scope](/concepts/scopes).

## Column types

Every column has a type, and the type is enforced when you write values. Pick the type that matches the data — it keeps your writes honest and makes the values render correctly in the app.

| Type      | Holds               | Example value                                       |
| --------- | ------------------- | --------------------------------------------------- |
| `string`  | Free text (default) | `"warm"`                                            |
| `number`  | Numeric values      | `87`                                                |
| `date`    | Calendar dates      | `"2026-08-01"`                                      |
| `boolean` | True/false flags    | `true`                                              |
| `url`     | A labeled link      | `{ "label": "CRM record", "value": "https://..." }` |

<Note>
  Enum (picklist) columns exist in the Fuse app but cannot be created through the API yet. If you need a constrained vocabulary today, use a `string` column and enforce the vocabulary on your side.
</Note>

## Create a column

`POST /business/lists/{listId}/columns` with a `name` (max 100 characters) and an optional `type`. If you omit `type`, you get a `string` column.

```bash theme={null}
curl -X POST https://api.tryfuse.ai/api/v1/business/lists/68a1f20b9c41d20014b3e901/columns \
  -u "af_YOUR_KEY:" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Lead Score", "type": "string" }'
```

```json theme={null}
{
  "column": {
    "id": "68a1f3009c41d20014b3e9a1",
    "name": "Lead Score",
    "type": "string"
  }
}
```

Hold on to the `id` — every later write targets the column by id, and row reads key values by it too.

Column names must be unique within a list, and the match is case- and whitespace-insensitive: `"Lead Score"`, `"lead score"`, and `"  Lead Score "` all collide. This prevents near-duplicate columns that look distinct but would be indistinguishable to a human scanning the list. A duplicate name returns:

```json theme={null}
{
  "error": {
    "code": "COLUMN_NAME_TAKEN",
    "message": "A column with this name already exists on the list.",
    "param": "name",
    "doc_url": "https://fuseai.com/api/errors/column_name_taken",
    "request_id": "req_8fK2mQ1xT4"
  }
}
```

That is a `409` in the standard [error envelope](/concepts/errors). If your integration creates columns idempotently, treat `COLUMN_NAME_TAKEN` as "already exists" and fetch the existing definition instead.

## List a list's columns

`GET /business/lists/{listId}/columns` returns every custom column defined on the list:

```bash theme={null}
curl https://api.tryfuse.ai/api/v1/business/lists/68a1f20b9c41d20014b3e901/columns \
  -u "af_YOUR_KEY:"
```

```json theme={null}
{
  "columns": [
    {
      "id": "68a1f3009c41d20014b3e9a1",
      "name": "Lead Score",
      "type": "string"
    }
  ]
}
```

Use this to build a name → id map when you did not create the columns yourself, or to recover an id after a `409` on create.

## Write values

`PATCH /business/lists/{listId}/columns/{columnId}/values` writes cells in bulk. Each entry pairs a `contactId` (from the list's [rows](/concepts/pagination)) with a `value`. You can send up to **500 rows per call**; batch larger updates across multiple requests.

<CodeGroup>
  ```bash string / number / boolean / date theme={null}
  curl -X PATCH https://api.tryfuse.ai/api/v1/business/lists/68a1f20b9c41d20014b3e901/columns/68a1f3009c41d20014b3e9a1/values \
    -u "af_YOUR_KEY:" \
    -H "Content-Type: application/json" \
    -d '{
      "values": [
        { "contactId": "68a1f2c89c41d20014b3e955", "value": "warm" },
        { "contactId": "68a1f2c89c41d20014b3e956", "value": "hot" }
      ]
    }'
  ```

  ```bash url column theme={null}
  curl -X PATCH https://api.tryfuse.ai/api/v1/business/lists/68a1f20b9c41d20014b3e901/columns/68a1f4119c41d20014b3ea22/values \
    -u "af_YOUR_KEY:" \
    -H "Content-Type: application/json" \
    -d '{
      "values": [
        {
          "contactId": "68a1f2c89c41d20014b3e955",
          "value": { "label": "Company site", "value": "https://brightpay.com" }
        }
      ]
    }'
  ```
</CodeGroup>

```json theme={null}
{ "updatedCount": 2 }
```

### How values are validated

For `string`, `number`, `boolean`, and `date` columns, send either the native JSON type or a string — strings are coerced against the column's type. `"42"` is accepted by a number column, `"true"` by a boolean column, `"2026-08-01"` by a date column. This exists so you can pipe values straight out of a CSV or a spreadsheet without converting types first.

`url` columns are stricter: the value must be an object with both `label` and `value`, each non-empty and at most 5,000 characters. A bare URL string is rejected — the label is what renders as the link text in the app, so the API refuses writes that would produce an unlabeled link.

To clear a cell, send `""` (an empty string) as the value.

<Warning>
  A value that fails coercion — say `"maybe"` sent to a boolean column — rejects the whole request with a `422`. Fix or drop the offending row and resend the batch.
</Warning>

## Read values back

Column values come back on `GET /business/lists/{listId}/rows`, in each row's `columnValues` object — keyed by **column id**, not name:

```json theme={null}
{
  "data": [
    {
      "id": "68a1f2c89c41d20014b3e955",
      "firstName": "Ada",
      "lastName": "Nwosu",
      "jobTitle": "Chief Financial Officer",
      "companyName": "Brightpay",
      "columnValues": {
        "68a1f3009c41d20014b3e9a1": "warm"
      }
    }
  ],
  "pagination": { "pageNum": 1, "totalPages": 5, "totalRecords": 412 }
}
```

To present values by name, fetch the column definitions once and map ids to names client-side.

## Use column values in campaigns

Custom column values are available as merge tokens in campaign templates: `{Column: Lead Score}` renders each contact's cell value into the email at send time. This makes columns the bridge between your data pipeline and personalized outreach — write a value per contact, reference it in the template, and every recipient gets their own. See [campaigns](/guides/campaigns) for how templates and variables work.

## Where to next

<CardGroup cols={2}>
  <Card title="Export a list" icon="file-csv" href="/guides/export-a-list">
    Get the list — and the data you attached to it — out as a CSV.
  </Card>

  <Card title="Campaigns end to end" icon="paper-plane" href="/guides/campaigns">
    Reference your columns as merge tokens in outbound sequences.
  </Card>

  <Card title="Upload your contacts" icon="upload" href="/guides/upload-your-contacts">
    Bring your own contacts, then annotate them with columns.
  </Card>

  <Card title="Errors" icon="triangle-exclamation" href="/concepts/errors">
    The envelope behind COLUMN\_NAME\_TAKEN and every other failure.
  </Card>
</CardGroup>
