# Create a feature

> The features.create operation: POST /api/v1/features in the REST API and the features_create tool in the MCP server.

Defines a feature of the brand, in the request's mode: something a product grants, such as `feature.export`
(a boolean), `seats.max` (a limit) or `support.level` (one of a set of values). Products grant values of it
in their `entitlements`, and customers' entitlements are resolved from what they've bought.

`combine` says how values from several products merge: `or` or `first` for booleans, `max`, `sum` or `first`
for limits, `first` for enums. The key and type never change, and enum values can be added but not removed.
A `perUnit` limit is multiplied by the subscription item's or purchase's quantity.

## At a glance

- **REST:** `POST /api/v1/features`
- **MCP tool:** `features_create`
- **Operation ID:** `features.create`
- **Scope:** Brand
- **Permission:** `catalog:write`
- **Risk tier:** `write`
- **Dry run:** Supported (send `Ownrecord-Dry-Run: true`)
- **Idempotency:** `Idempotency-Key` required
- **Modes:** Test and live
- **Authentication:** API key with the `catalog:write` scope
- **Since:** M4

## Request

### Body

A JSON object (`Content-Type: application/json`):

| Field          | Type                               | Required | Description                                                                                                                              |
| -------------- | ---------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `key`          | string                             | Yes      | Unique in the brand and mode, e.g. `seats.max`. Never changes.                                                                           |
| `name`         | string                             | Yes      | Shown on the pricing table, e.g. Seats.                                                                                                  |
| `type`         | one of `boolean`, `limit`, `enum`  | Yes      | `boolean` (on or off), `limit` (a whole number) or `enum` (one of a set of values).                                                      |
| `enumValues`   | array of string                    | No       | Enum features: the allowed values, in display order.                                                                                     |
| `combine`      | one of `max`, `sum`, `or`, `first` | No       | Default: `or` for booleans, `max` for limits, `first` for enums.                                                                         |
| `defaultValue` | boolean or number or string        | No       | What customers without the feature get. Default: false, 0, or the first enum value.                                                      |
| `perUnit`      | boolean                            | No       | Limit features only: multiply the value a product grants by the quantity bought or subscribed (e.g. 5 projects per seat). Default false. |

## Response

`201 Created` with a JSON object:

| Field          | Type                               | Description                                                                                   |
| -------------- | ---------------------------------- | --------------------------------------------------------------------------------------------- |
| `id`           | `feat_…` ID                        |                                                                                               |
| `object`       | `"feature"`                        |                                                                                               |
| `brandId`      | `brd_…` ID                         |                                                                                               |
| `livemode`     | boolean                            |                                                                                               |
| `key`          | string                             |                                                                                               |
| `name`         | string                             |                                                                                               |
| `type`         | one of `boolean`, `limit`, `enum`  |                                                                                               |
| `enumValues`   | array of string or null            |                                                                                               |
| `combine`      | one of `max`, `sum`, `or`, `first` |                                                                                               |
| `defaultValue` | boolean or number or string        |                                                                                               |
| `perUnit`      | boolean                            | Limit features only: values are multiplied by the item's or purchase's quantity (spec §36.1). |
| `createdAt`    | date-time                          |                                                                                               |
| `updatedAt`    | date-time                          |                                                                                               |

## Examples

### A seat limit, per unit

```http
POST /api/v1/features
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json

{
  "key": "seats.max",
  "name": "Seats",
  "type": "limit",
  "defaultValue": 1,
  "perUnit": true
}
```

Response (`201`):

```json
{
  "id": "feat_01m4tx5nj8emwbyf7y9vcf47y6",
  "object": "feature",
  "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
  "livemode": false,
  "key": "seats.max",
  "name": "Seats",
  "type": "limit",
  "enumValues": null,
  "combine": "max",
  "defaultValue": 1,
  "perUnit": true,
  "createdAt": "2026-10-13T12:00:00.000Z",
  "updatedAt": "2026-10-13T12:00:00.000Z"
}
```

## Errors

| Code                                                                                   | HTTP status | When                                                                                                                                               | What to do                                                                      |
| -------------------------------------------------------------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed) | 400         | A field is malformed, the combine rule doesn't fit the type, the default isn't a valid value, or `perUnit` is set on a feature that isn't a limit. | Fix the fields listed in `details.issues` and send the request again.           |
| [`feature_key_taken`](https://ownrecord.io/docs/reference/errors.md#feature_key_taken) | 409         | The brand already has a feature with this key in this mode.                                                                                        | Choose another key, or update the existing feature.                             |
| [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied) | 403         | You can't change this brand's catalog.                                                                                                             | Ask an organization owner or admin for a role or scope that allows this action. |

It can also return these [common errors](https://ownrecord.io/docs/reference.md#common-errors): [`api_version_unsupported`](https://ownrecord.io/docs/reference/errors.md#api_version_unsupported), [`invalid_json`](https://ownrecord.io/docs/reference/errors.md#invalid_json), [`unsupported_media_type`](https://ownrecord.io/docs/reference/errors.md#unsupported_media_type), [`authentication_required`](https://ownrecord.io/docs/reference/errors.md#authentication_required), [`api_key_invalid`](https://ownrecord.io/docs/reference/errors.md#api_key_invalid), [`scope_required`](https://ownrecord.io/docs/reference/errors.md#scope_required), [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found), [`idempotency_key_required`](https://ownrecord.io/docs/reference/errors.md#idempotency_key_required), [`idempotency_key_reused`](https://ownrecord.io/docs/reference/errors.md#idempotency_key_reused), [`idempotency_in_progress`](https://ownrecord.io/docs/reference/errors.md#idempotency_in_progress), [`approval_required`](https://ownrecord.io/docs/reference/errors.md#approval_required), [`approval_rejected`](https://ownrecord.io/docs/reference/errors.md#approval_rejected), [`approval_expired`](https://ownrecord.io/docs/reference/errors.md#approval_expired), [`stale_approval`](https://ownrecord.io/docs/reference/errors.md#stale_approval), [`approval_policy_denied`](https://ownrecord.io/docs/reference/errors.md#approval_policy_denied), [`rate_limited`](https://ownrecord.io/docs/reference/errors.md#rate_limited), [`internal_error`](https://ownrecord.io/docs/reference/errors.md#internal_error).
