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

# Profile Custom Fields API

> Send and retrieve organization-defined profile values through the Profiles API.

Use profile custom fields to exchange organization-specific profile data through the Minerva Profiles API. Definitions belong to your organization and apply across all its workspaces. Requests use immutable definition keys, while responses include both keys and customer-facing labels.

For administrator workflows, batch uploads, list columns, and profile views, see the [Profile Custom Fields Guide](/profile-custom-fields-guide).

<Warning>
  This capability is not yet released to production. The Profiles API, dashboard
  stories, and this documentation are being prepared together. Do not deploy an
  integration until your Minerva technical contact confirms that the feature is
  available for your organization.
</Warning>

## Prerequisites and key discovery

You need an application API key for the target organization and workspace. Send it in the `x-api-key` header.

An administrator creates definitions under **Administration** > **Configuration** > **Profile Custom Fields**. Record each definition's immutable key and, for Choice fields, each option's stored value. Use keys in requests. Do not use labels as JSON property names because labels can be renamed.

```http theme={null}
x-api-key: YOUR_API_KEY
```

The examples below use `https://api.gominerva.com/clm/v1`. See the [API Reference](/api-reference/introduction) for authentication and generated endpoint pages.

## Request contract

`profileCustomFields` is a JSON object whose property names are definition keys. Each value must use the JSON type required by that definition.

```json theme={null}
{
  "profileCustomFields": {
    "loan_number": "LN-2026-0042",
    "loan_status": "funded",
    "servicing_details": {
      "portfolio": "Prime",
      "boardingDate": "2026-08-27"
    }
  }
}
```

Use the object on these operations:

| Operation                     | Behavior                                                                                                  |
| ----------------------------- | --------------------------------------------------------------------------------------------------------- |
| `POST /profiles`              | Creates a profile without an initial screen. All required custom fields must be present and non-null.     |
| `POST /onboarding/profiles`   | Creates and screens a profile. All required custom fields must be present and non-null.                   |
| `PATCH /profiles/{profileId}` | Updates only the custom keys supplied. Omitted custom keys are preserved. `null` clears one stored value. |

On create or onboarding, `null` is equivalent to omitting an optional custom value and fails validation when the field is required. A PATCH validates the custom fields supplied in that request. Custom fields omitted from the request remain unchanged, and `null` clears one named value. Required custom fields that are not included are not checked again.

<Warning>
  Writes use the latest definitions. An archived or unknown key, a wrong JSON
  type, an invalid Choice value, or an invalid Date returns HTTP 400. If Minerva
  cannot retrieve the definitions, it rejects the write instead of saving an
  unvalidated value. Fetch the latest definitions before sending values, and
  treat configuration changes as an integration contract change.
</Warning>

### Type matrix

| UI label        | API `type` | Request JSON value              | Example                 |
| --------------- | ---------- | ------------------------------- | ----------------------- |
| Text            | `text`     | String                          | `"LN-2026-0042"`        |
| Number          | `number`   | JSON number, integer or decimal | `4417723` or `12.75`    |
| Date            | `date`     | String in `YYYY-MM-DD` format   | `"2026-08-27"`          |
| Yes/No          | `boolean`  | Boolean                         | `true`                  |
| Choice          | `enum`     | Configured stored option value  | `"funded"`              |
| Structured data | `json`     | JSON object or array            | `{"portfolio":"Prime"}` |

Text values support up to 4,000 characters. Structured data must be an object or array and is limited to 16 KB per field. The combined custom values on one profile are limited to 64 KB. Exact integral Number values are accepted through the signed 64-bit range.

Choice requests use the option's stored value, such as `funded`, not its display label, such as `Funded`. After a Choice definition is saved, each existing option value remains valid and cannot be changed or removed in this release. Administrators can add options, change display labels, and reorder options. This preserves the identifiers already stored on profiles so historical values remain readable.

If the vocabulary must be replaced, contact [Minerva Support](mailto:support@gominerva.com). Archiving the field and creating a new one is another option, but the new field starts empty and does not inherit existing profile values.

### Create a profile

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.gominerva.com/clm/v1/profiles" \
    -H "x-api-key: YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    --data '{
      "name": "Alex Morgan",
      "kind": "individual",
      "profileCustomFields": {
        "loan_number": "LN-2026-0042",
        "loan_status": "funded",
        "servicing_details": {
          "portfolio": "Prime",
          "boardingDate": "2026-08-27"
        }
      }
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch("https://api.gominerva.com/clm/v1/profiles", {
    method: "POST",
    headers: {
      "x-api-key": process.env.MINERVA_API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      name: "Alex Morgan",
      kind: "individual",
      profileCustomFields: {
        loan_number: "LN-2026-0042",
        loan_status: "funded",
        servicing_details: {
          portfolio: "Prime",
          boardingDate: "2026-08-27",
        },
      },
    }),
  });

  if (!response.ok) {
    const error = await response.text();
    throw new Error(`Minerva API returned ${response.status}: ${error}`);
  }

  const data = await response.json();
  console.log(data.result.profile.id);
  ```
</CodeGroup>

### Create and screen a profile

Use `POST /onboarding/profiles` when profile creation should also perform the configured onboarding screen.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.gominerva.com/clm/v1/onboarding/profiles" \
    -H "x-api-key: YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    --data '{
      "name": "Alex Morgan",
      "kind": "individual",
      "profileCustomFields": {
        "loan_number": "LN-2026-0042",
        "loan_status": "funded",
        "servicing_details": {
          "portfolio": "Prime",
          "boardingDate": "2026-08-27"
        }
      }
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    "https://api.gominerva.com/clm/v1/onboarding/profiles",
    {
      method: "POST",
      headers: {
        "x-api-key": process.env.MINERVA_API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        name: "Alex Morgan",
        kind: "individual",
        profileCustomFields: {
          loan_number: "LN-2026-0042",
          loan_status: "funded",
          servicing_details: {
            portfolio: "Prime",
            boardingDate: "2026-08-27",
          },
        },
      }),
    },
  );

  if (!response.ok) {
    const error = await response.text();
    throw new Error(`Minerva API returned ${response.status}: ${error}`);
  }

  const data = await response.json();
  console.log(data.result.profile.id, data.result.tasks);
  ```
</CodeGroup>

### Update or clear values

PATCH updates only the profile fields and custom field keys supplied in the request. Omitted custom fields remain unchanged, and `null` clears one named value.

```bash cURL theme={null}
curl -X PATCH \
  "https://api.gominerva.com/clm/v1/profiles/66c391b92888a0db5cc6d3f6" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{
    "profileCustomFields": {
      "loan_status": "paid_out",
      "servicing_details": null
    }
  }'
```

This changes Loan Status, clears Servicing Details, and preserves Loan Number and every other custom value.

```javascript JavaScript theme={null}
const profileId = "66c391b92888a0db5cc6d3f6";
const response = await fetch(
  `https://api.gominerva.com/clm/v1/profiles/${profileId}`,
  {
    method: "PATCH",
    headers: {
      "x-api-key": process.env.MINERVA_API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      profileCustomFields: {
        loan_status: "paid_out",
        servicing_details: null,
      },
    }),
  },
);

if (!response.ok) {
  throw new Error(`Minerva API returned ${response.status}`);
}
```

## Response contract

Single-profile and list responses can include an optional `profileCustomFields` array. Items are ordered by definition priority.

<Info>
  Profile reads may take up to 60 seconds to reflect a definition edit or
  archive. During that period, `GET /profiles/{profileId}`, `GET /profiles`, and
  dashboard read views may return the prior field or Choice option label, or may
  still include a newly archived field. Stored profile values do not change.
  After the read view refreshes, renamed labels are updated and archived fields
  are omitted. Profile writes always validate against the latest definition.
</Info>

```json theme={null}
{
  "msg": "OK",
  "result": {
    "profile": {
      "id": "66c391b92888a0db5cc6d3f6",
      "name": "Alex Morgan",
      "profileCustomFields": [
        {
          "key": "loan_number",
          "label": "Loan Number",
          "type": "text",
          "value": "LN-2026-0042"
        },
        {
          "key": "loan_status",
          "label": "Loan Status",
          "type": "enum",
          "value": "funded",
          "valueLabel": "Funded"
        },
        {
          "key": "servicing_details",
          "label": "Servicing Details",
          "type": "json",
          "value": {
            "portfolio": "Prime",
            "boardingDate": "2026-08-27"
          }
        }
      ]
    }
  },
  "status": 200
}
```

For Choice fields, `value` is the stable stored option value and `valueLabel` is the display label resolved for that response. Store or compare `value`; render `valueLabel` when present. After an administrator changes a field label or Choice option label, profile reads can return the previous label for up to 60 seconds. Profile writes always validate against the latest definition.

The list operation returns the same optional array on each item:

```json theme={null}
{
  "msg": "OK",
  "result": {
    "profiles": [
      {
        "id": "66c391b92888a0db5cc6d3f6",
        "name": "Alex Morgan",
        "profileCustomFields": [
          {
            "key": "loan_number",
            "label": "Loan Number",
            "type": "number",
            "value": 9223372036854775807
          },
          {
            "key": "loan_status",
            "label": "Loan Status",
            "type": "enum",
            "value": "funded",
            "valueLabel": "Funded"
          }
        ]
      }
    ],
    "meta": {
      "page": 1,
      "perPage": 20,
      "total": 1
    }
  },
  "status": 200
}
```

<Warning>
  Large integral Number values are emitted exactly as JSON numbers. JavaScript's
  default `JSON.parse` rounds integers above `Number.MAX_SAFE_INTEGER`
  (`9007199254740991`). Use a lossless JSON parser before converting the
  response to JavaScript values. When a business identifier does not need
  arithmetic, define it as Text at the integration boundary instead. The Minerva
  API still requires a JSON number for fields whose definition type is Number.
</Warning>

## Validation errors

The response follows the standard Minerva error shape, and the message identifies the custom field path when available. For profile writes, HTTP 400 means the request is malformed or a value does not match the current definition; correct the request before retrying. HTTP 409 means a Text value exceeds 4,000 characters, a Structured data value exceeds 16 KB, or the combined custom values exceed 64 KB; shorten the values before retrying. Separately, a definition-management request returns HTTP 409 if it omits or changes a saved Choice option value. Retain the complete saved Choice set, with any additions or label and order changes, before retrying that request.

Common failures include:

| Failure                             | Status | Example                             | Correction                                                                |
| ----------------------------------- | ------ | ----------------------------------- | ------------------------------------------------------------------------- |
| Malformed JSON                      | 400    | Missing comma or closing brace      | Correct the JSON document before retrying.                                |
| Unknown or archived key             | 400    | `profileCustomFields.old_status`    | Refresh active definitions and stop sending the retired key.              |
| Wrong JSON type                     | 400    | `"amount": "125.50"` for Number     | Send `125.50` as a JSON number.                                           |
| Invalid Date                        | 400    | `"closing_date": "08/27/2026"`      | Send `"2026-08-27"`.                                                      |
| Invalid Choice                      | 400    | `"loan_status": "Funded"`           | Send the stored option value, such as `"funded"`.                         |
| Missing required value on create    | 400    | `loan_number` omitted               | Include a non-null value in create or onboarding.                         |
| Invalid Structured data             | 400    | `"servicing_details": "Prime"`      | Send a JSON object or array.                                              |
| Text exceeds 4,000 characters       | 409    | `loan_notes` has 4,001 characters   | Shorten the Text value, then retry.                                       |
| Structured data exceeds 16 KB       | 409    | `servicing_details` exceeds 16 KB   | Shorten the object or array, then retry.                                  |
| Combined custom values exceed 64 KB | 409    | `profileCustomFields` exceeds 64 KB | Remove or shorten custom values, then retry.                              |
| Existing Choice value omitted       | 409    | Definition update omits `funded`    | Keep `funded`; change its label or order, and add new values when needed. |

Example error handling:

```javascript JavaScript theme={null}
const response = await fetch(
  "https://api.gominerva.com/clm/v1/profiles",
  options,
);

if (response.status === 400) {
  const error = await response.json();
  console.error(
    "Correct the profile custom field request before retrying",
    error,
  );
} else if (response.status === 409) {
  const error = await response.json();
  console.error(
    "Shorten the profile custom field values before retrying",
    error,
  );
} else if (!response.ok) {
  throw new Error(`Minerva API returned ${response.status}`);
}
```

## Required-field rollout guidance

After a definition becomes required, the next profile create or onboarding request must include a valid non-null value. The change does not backfill existing profiles or make a PATCH fail when that required custom field is omitted. Supplied PATCH values still validate against the latest type, archive status, and Choice vocabulary.

Before an administrator enables Required:

1. Inventory every profile creation path, including direct create, onboarding, CSV, and XLSX.
2. Update producers to send the immutable key with a correctly typed value.
3. Test missing, `null`, wrong-type, unknown-key, and archived-key requests.
4. Coordinate the activation time with the administrator.
5. Monitor HTTP 400 responses after activation. Prefer a forward edit that makes the field optional again. Historical rollback replays an old snapshot through current validation and can return HTTP 409 if it would omit a saved Choice value; see [Archive, history, and rollback](/profile-custom-fields-guide#archive-history-and-rollback).

Optional is the safer default when not every source system has the value.

## Backward compatibility

`profileCustomFields` is optional in both requests and responses. Existing clients can continue sending the old profile request shape. Responses omit the array when no stored values resolve to active definitions, preserving the previous response shape. Stored values are retained when a definition is archived. After the read view refreshes, archived definitions and values with no active definition are not emitted.

Clients that deserialize with strict schemas should allow the optional array before the feature is enabled. Each array item contains `key`, `label`, `type`, and `value`; `valueLabel` is optional and is used for Choice fields.

## Endpoint reference

Use the **Profiles - Endpoints** section of the [API Reference](/api-reference/introduction) for generated schemas and operation details:

* Create Profile: `POST /profiles`
* Create Profile (Onboarding): `POST /onboarding/profiles`
* Update Profile: `PATCH /profiles/{profileId}`
* Get Profile Details: `GET /profiles/{profileId}`
* List profiles: `GET /profiles`
