# Products tools

> MCP tools for Products: products_archive, products_create, products_get, products_list, products_pricing_table, products_update.

- `products_archive`: Archive a product
- `products_create`: Create a product
- `products_get`: Get a product
- `products_list`: List products
- `products_pricing_table`: Get the pricing table
- `products_update`: Update a product

## `products_archive`

**Archive a product.** Operation [`products.archive`](https://ownrecord.io/docs/reference/products-archive.md) · Brand level · permission `catalog:write` · risk tier `write` · since M4

Archives a product: it leaves the pricing table, can't be sold and can't get new prices. Existing
subscriptions, invoices and the product's prices stay as they are. Archiving an archived product changes
nothing. Reactivate it with `products.update` and `active: true`.

`reason` is recorded in the audit log.

Annotations: `readOnlyHint: false` · `destructiveHint: true` · `idempotentHint: true` · `openWorldHint: false`

### Arguments

| Argument          | Type          | Required | Description                                                                                                                                                                                              |
| ----------------- | ------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`              | ID (`prod_…`) | yes      | –                                                                                                                                                                                                        |
| `reason`          | string        | no       | (1–500 characters)                                                                                                                                                                                       |
| `entity_id`       | ID (`ent_…`)  | no       | Optional: the brand's legal entity (ent\_…); `brand_id` alone is enough.                                                                                                                                 |
| `brand_id`        | ID (`brd_…`)  | no       | The brand to act on (brd\_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand.                                                                 |
| `idempotency_key` | string        | no       | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's `idempotency_key`. (1–255 characters) |
| `dry_run`         | boolean       | no       | true: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default `false`)                                                                                 |

- **Brand:** pass `brand_id` with an organization or entity key; a brand key acts on its own brand.
- **Idempotency:** the result returns the `idempotency_key` used, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours).
- **Dry run:** supported. `dry_run: true` checks permissions and rules and computes the result, then rolls everything back.

### Errors

- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): No product with this ID is visible to the request.
- [`version_conflict`](https://ownrecord.io/docs/reference/errors.md#version_conflict): The product changed at the same time; retry.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): You can't change this brand's catalog.

### Example

**Retire a plan**

Arguments:

```json
{
  "id": "prod_01m4tx5nj8emwbyf7y9vcf47y0",
  "reason": "Replaced by the 2027 plans",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "id": "prod_01m4tx5nj8emwbyf7y9vcf47y0",
  "object": "product",
  "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
  "livemode": false,
  "name": "Pro",
  "description": "For growing teams.",
  "taxCategoryId": "saas",
  "kind": "plan",
  "entitlements": [
    {
      "featureKey": "seats.max",
      "value": 10
    },
    {
      "featureKey": "feature.export",
      "value": true
    }
  ],
  "lifetimeEntitlements": false,
  "displayOrder": 2,
  "highlighted": true,
  "active": false,
  "images": [],
  "metadata": {},
  "createdAt": "2026-10-13T12:00:00.000Z",
  "updatedAt": "2026-10-14T09:30:00.000Z"
}
```

## `products_create`

**Create a product.** Operation [`products.create`](https://ownrecord.io/docs/reference/products-create.md) · Brand level · permission `catalog:write` · risk tier `write` · since M4

Creates a product in the brand's catalog, in the request's mode (test and live mode have separate catalogs).

`taxCategoryId` is required: it decides which tax rates apply to the product in every jurisdiction (for
example `saas`, `ebook` or `electronically_supplied_service`). Products of `kind: plan` appear on the brand's
public pricing table. `entitlements` grant values of the brand's features (`features.create`), such as
`seats.max = 10`; each value must fit its feature's type. Subscriptions grant them while they're active; with
`lifetimeEntitlements: true`, a one-time purchase grants them for good.

Add prices with `prices.create`. Images are https URLs.

Annotations: `readOnlyHint: false` · `destructiveHint: false` · `idempotentHint: false` · `openWorldHint: false`

### Arguments

| Argument               | Type                                                                     | Required | Description                                                                                                                                                                                              |
| ---------------------- | ------------------------------------------------------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`                 | string                                                                   | yes      | The product's name, shown to customers. (1–200 characters)                                                                                                                                               |
| `description`          | string                                                                   | no       | Shown on checkout pages and the pricing table. (1–2000 characters)                                                                                                                                       |
| `taxCategoryId`        | one of 9 values, e.g. `electronically_supplied_service`, `saas`, `ebook` | yes      | The product's tax category (spec §7.2), e.g. `saas` or `electronically_supplied_service`.                                                                                                                |
| `kind`                 | one of `standard`, `plan`                                                | no       | `plan` products appear on the brand's pricing table; `standard` ones don't. (default `"standard"`)                                                                                                       |
| `entitlements`         | array of object                                                          | no       | Feature values the product grants (spec §36.1), each feature once. (at most 100 items) (default `[]`)                                                                                                    |
| `lifetimeEntitlements` | boolean                                                                  | no       | true: a one-time purchase of the product grants its entitlements for good (spec §36.1). Default false.                                                                                                   |
| `displayOrder`         | integer                                                                  | no       | Position on the pricing table, lowest first. (0–10000)                                                                                                                                                   |
| `highlighted`          | boolean                                                                  | no       | Highlight the plan on the pricing table.                                                                                                                                                                 |
| `images`               | array of string                                                          | no       | Image URLs (https). On updates the whole list is replaced. (at most 8 items)                                                                                                                             |
| `metadata`             | object                                                                   | no       | Your own key-value pairs (at most 50 keys). On updates the whole set is replaced.                                                                                                                        |
| `entity_id`            | ID (`ent_…`)                                                             | no       | Optional: the brand's legal entity (ent\_…); `brand_id` alone is enough.                                                                                                                                 |
| `brand_id`             | ID (`brd_…`)                                                             | no       | The brand to act on (brd\_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand.                                                                 |
| `idempotency_key`      | string                                                                   | no       | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's `idempotency_key`. (1–255 characters) |
| `dry_run`              | boolean                                                                  | no       | true: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default `false`)                                                                                 |

- **Brand:** pass `brand_id` with an organization or entity key; a brand key acts on its own brand.
- **Idempotency:** the result returns the `idempotency_key` used, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours).
- **Dry run:** supported. `dry_run: true` checks permissions and rules and computes the result, then rolls everything back.

### Errors

- [`org_restricted`](https://ownrecord.io/docs/reference/errors.md#org_restricted): Live mode, while the organization's Ownrecord subscription is unpaid (§37.5).
- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): A field is missing or malformed, e.g. an unknown tax category.
- [`feature_not_found`](https://ownrecord.io/docs/reference/errors.md#feature_not_found): An entitlement names a feature the brand doesn't have in this mode.
- [`entitlement_value_invalid`](https://ownrecord.io/docs/reference/errors.md#entitlement_value_invalid): An entitlement value doesn't fit its feature's type.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): You can't change this brand's catalog.

### Example

**A plan with seats and export**

Arguments:

```json
{
  "name": "Pro",
  "description": "For growing teams.",
  "taxCategoryId": "saas",
  "kind": "plan",
  "entitlements": [
    {
      "featureKey": "seats.max",
      "value": 10
    },
    {
      "featureKey": "feature.export",
      "value": true
    }
  ],
  "displayOrder": 2,
  "highlighted": true,
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "id": "prod_01m4tx5nj8emwbyf7y9vcf47y0",
  "object": "product",
  "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
  "livemode": false,
  "name": "Pro",
  "description": "For growing teams.",
  "taxCategoryId": "saas",
  "kind": "plan",
  "entitlements": [
    {
      "featureKey": "seats.max",
      "value": 10
    },
    {
      "featureKey": "feature.export",
      "value": true
    }
  ],
  "lifetimeEntitlements": false,
  "displayOrder": 2,
  "highlighted": true,
  "active": true,
  "images": [],
  "metadata": {},
  "createdAt": "2026-10-13T12:00:00.000Z",
  "updatedAt": "2026-10-13T12:00:00.000Z"
}
```

## `products_get`

**Get a product.** Operation [`products.get`](https://ownrecord.io/docs/reference/products-get.md) · Brand level · permission `catalog:read` · risk tier `read` · since M4

Returns one product of the brand's catalog in the request's mode, archived or not: its kind, whether it is
`active`, its entitlements and its pricing-table hints (`displayOrder`, `highlighted`). The brand's pricing
table shows its active products of kind `plan`.

Annotations: `readOnlyHint: true` · `destructiveHint: false` · `idempotentHint: true` · `openWorldHint: false`

### Arguments

| Argument    | Type          | Required | Description                                                                                                                              |
| ----------- | ------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `id`        | ID (`prod_…`) | yes      | –                                                                                                                                        |
| `entity_id` | ID (`ent_…`)  | no       | Optional: the brand's legal entity (ent\_…); `brand_id` alone is enough.                                                                 |
| `brand_id`  | ID (`brd_…`)  | no       | The brand to act on (brd\_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |

- **Brand:** pass `brand_id` with an organization or entity key; a brand key acts on its own brand.
- **Read-only:** changes nothing; `idempotency_key` is ignored.

### Errors

- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): No product with this ID is visible to the request.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): You can't see this brand's catalog.

### Example

**A plan**

Arguments:

```json
{
  "id": "prod_01m4tx5nj8emwbyf7y9vcf47y0",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
```

Result `output`:

```json
{
  "id": "prod_01m4tx5nj8emwbyf7y9vcf47y0",
  "object": "product",
  "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
  "livemode": false,
  "name": "Pro",
  "description": "For growing teams.",
  "taxCategoryId": "saas",
  "kind": "plan",
  "entitlements": [
    {
      "featureKey": "seats.max",
      "value": 10
    },
    {
      "featureKey": "feature.export",
      "value": true
    }
  ],
  "lifetimeEntitlements": false,
  "displayOrder": 2,
  "highlighted": true,
  "active": true,
  "images": [],
  "metadata": {},
  "createdAt": "2026-10-13T12:00:00.000Z",
  "updatedAt": "2026-10-13T12:00:00.000Z"
}
```

## `products_list`

**List products.** Operation [`products.list`](https://ownrecord.io/docs/reference/products-list.md) · Brand level · permission `catalog:read` · risk tier `read` · since M4

Lists the brand's products in the request's mode, newest first. Filter with `active` (`true` or `false`) and
`kind` (`plan` or `standard`).

Paginate with `limit` (1–100, default 10) and `starting_after` (the `next_cursor` of the previous page).

Annotations: `readOnlyHint: true` · `destructiveHint: false` · `idempotentHint: true` · `openWorldHint: false`

### Arguments

| Argument         | Type                      | Required | Description                                                                                                                              |
| ---------------- | ------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `active`         | string                    | no       | Only active (`true`) or archived (`false`) products.                                                                                     |
| `kind`           | one of `standard`, `plan` | no       | –                                                                                                                                        |
| `limit`          | integer                   | no       | (1–100)                                                                                                                                  |
| `starting_after` | ID (`prod_…`)             | no       | –                                                                                                                                        |
| `entity_id`      | ID (`ent_…`)              | no       | Optional: the brand's legal entity (ent\_…); `brand_id` alone is enough.                                                                 |
| `brand_id`       | ID (`brd_…`)              | no       | The brand to act on (brd\_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |

- **Brand:** pass `brand_id` with an organization or entity key; a brand key acts on its own brand.
- **Read-only:** changes nothing; `idempotency_key` is ignored.

### Errors

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): A filter or pagination value is malformed.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): You can't see this brand's catalog.

### Example

**Active plans**

Arguments:

```json
{
  "active": "true",
  "kind": "plan",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
```

Result `output`:

```json
{
  "object": "list",
  "data": [
    {
      "id": "prod_01m4tx5nj8emwbyf7y9vcf47y0",
      "object": "product",
      "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
      "livemode": false,
      "name": "Pro",
      "description": "For growing teams.",
      "taxCategoryId": "saas",
      "kind": "plan",
      "entitlements": [
        {
          "featureKey": "seats.max",
          "value": 10
        },
        {
          "featureKey": "feature.export",
          "value": true
        }
      ],
      "lifetimeEntitlements": false,
      "displayOrder": 2,
      "highlighted": true,
      "active": true,
      "images": [],
      "metadata": {},
      "createdAt": "2026-10-13T12:00:00.000Z",
      "updatedAt": "2026-10-13T12:00:00.000Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

## `products_pricing_table`

**Get the pricing table.** Operation [`products.pricing_table`](https://ownrecord.io/docs/reference/products-pricing-table.md) · Brand level · permission `catalog:read` · risk tier `read` · since M4

Returns the brand's pricing table: its active products of `kind: plan`, in display order, with their active
prices and the features each plan grants (by name). Use it to render plans on your own site; it needs no API
key and is rate-limited per IP address.

Only what customers may see is included: no nicknames, metadata or internal fields. Archived products and
prices are left out.

Annotations: `readOnlyHint: true` · `destructiveHint: false` · `idempotentHint: true` · `openWorldHint: false`

### Arguments

| Argument    | Type         | Required | Description                                                                                                                              |
| ----------- | ------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `brandSlug` | string       | yes      | The brand's slug, e.g. `acme-app`. (1–64 characters)                                                                                     |
| `entity_id` | ID (`ent_…`) | no       | Optional: the brand's legal entity (ent\_…); `brand_id` alone is enough.                                                                 |
| `brand_id`  | ID (`brd_…`) | no       | The brand to act on (brd\_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |

- **Brand:** pass `brand_id` with an organization or entity key; a brand key acts on its own brand.
- **Read-only:** changes nothing; `idempotency_key` is ignored.

### Errors

- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): No brand with this slug is visible to the request.

### Example

**Acme App's plans**

Arguments:

```json
{
  "brandSlug": "acme-app",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
```

Result `output`:

```json
{
  "object": "pricing_table",
  "brand": {
    "id": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
    "slug": "acme-app",
    "name": "Acme App"
  },
  "livemode": true,
  "products": [
    {
      "id": "prod_01m4tx5nj8emwbyf7y9vcf47y0",
      "name": "Pro",
      "description": "For growing teams.",
      "images": [],
      "highlighted": true,
      "displayOrder": 2,
      "features": [
        {
          "key": "seats.max",
          "name": "Seats",
          "type": "limit",
          "value": 10
        },
        {
          "key": "feature.export",
          "name": "Export",
          "type": "boolean",
          "value": true
        }
      ],
      "prices": [
        {
          "id": "price_01m4tx5nj8emwbyf7y9vcf47y2",
          "lookupKey": "pro_monthly",
          "type": "recurring",
          "currency": "EUR",
          "taxBehavior": "inclusive",
          "amount": null,
          "customAmount": null,
          "creditPack": null,
          "recurring": {
            "interval": "month",
            "intervalCount": 1,
            "usageType": "licensed",
            "billingScheme": "tiered",
            "unitAmount": null,
            "tiersMode": "graduated",
            "tiers": [
              {
                "upTo": 10,
                "unitAmountMicros": "12000000",
                "flatAmount": null
              },
              {
                "upTo": "inf",
                "unitAmountMicros": "9000000",
                "flatAmount": null
              }
            ],
            "trialDays": 14,
            "includedCredits": null,
            "metered": null
          }
        }
      ]
    }
  ]
}
```

## `products_update`

**Update a product.** Operation [`products.update`](https://ownrecord.io/docs/reference/products-update.md) · Brand level · permission `catalog:write` · risk tier `write` · since M4

Changes a product's name, description, tax category, kind, entitlements, `lifetimeEntitlements`, pricing-table
hints, images or metadata. Send only the fields to change; `null` clears an optional value. `entitlements`,
`images` and `metadata` replace the whole list or map. Customers' entitlements are recomputed after a change of
`entitlements` or `lifetimeEntitlements`.

A new tax category applies to future sales; issued invoices keep theirs. `active: false` archives the product
(as products.archive) and `active: true` reactivates it.

Annotations: `readOnlyHint: false` · `destructiveHint: false` · `idempotentHint: true` · `openWorldHint: false`

### Arguments

| Argument               | Type                                                                     | Required | Description                                                                                                                                                                                              |
| ---------------------- | ------------------------------------------------------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                   | ID (`prod_…`)                                                            | yes      | –                                                                                                                                                                                                        |
| `name`                 | string                                                                   | no       | (1–200 characters)                                                                                                                                                                                       |
| `description`          | string or null                                                           | no       | –                                                                                                                                                                                                        |
| `taxCategoryId`        | one of 9 values, e.g. `electronically_supplied_service`, `saas`, `ebook` | no       | The product's tax category (spec §7.2), e.g. `saas` or `electronically_supplied_service`.                                                                                                                |
| `kind`                 | one of `standard`, `plan`                                                | no       | `plan` products appear on the brand's pricing table; `standard` ones don't.                                                                                                                              |
| `entitlements`         | array of object                                                          | no       | Feature values the product grants (spec §36.1), each feature once. (at most 100 items)                                                                                                                   |
| `lifetimeEntitlements` | boolean                                                                  | no       | true: a one-time purchase grants the entitlements for good. Buyers' entitlements follow.                                                                                                                 |
| `displayOrder`         | integer or null                                                          | no       | –                                                                                                                                                                                                        |
| `highlighted`          | boolean                                                                  | no       | –                                                                                                                                                                                                        |
| `images`               | array of string                                                          | no       | Image URLs (https). On updates the whole list is replaced. (at most 8 items)                                                                                                                             |
| `metadata`             | object                                                                   | no       | Your own key-value pairs (at most 50 keys). On updates the whole set is replaced.                                                                                                                        |
| `active`               | boolean                                                                  | no       | false archives the product, true reactivates it.                                                                                                                                                         |
| `entity_id`            | ID (`ent_…`)                                                             | no       | Optional: the brand's legal entity (ent\_…); `brand_id` alone is enough.                                                                                                                                 |
| `brand_id`             | ID (`brd_…`)                                                             | no       | The brand to act on (brd\_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand.                                                                 |
| `idempotency_key`      | string                                                                   | no       | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's `idempotency_key`. (1–255 characters) |
| `dry_run`              | boolean                                                                  | no       | true: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default `false`)                                                                                 |

- **Brand:** pass `brand_id` with an organization or entity key; a brand key acts on its own brand.
- **Idempotency:** the result returns the `idempotency_key` used, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours).
- **Dry run:** supported. `dry_run: true` checks permissions and rules and computes the result, then rolls everything back.

### Errors

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): No field to change was sent, or one is malformed.
- [`feature_not_found`](https://ownrecord.io/docs/reference/errors.md#feature_not_found): An entitlement names a feature the brand doesn't have in this mode.
- [`entitlement_value_invalid`](https://ownrecord.io/docs/reference/errors.md#entitlement_value_invalid): An entitlement value doesn't fit its feature's type.
- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): No product with this ID is visible to the request.
- [`version_conflict`](https://ownrecord.io/docs/reference/errors.md#version_conflict): The product changed at the same time; retry.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): You can't change this brand's catalog.

### Example

**Raise the seat limit**

Arguments:

```json
{
  "id": "prod_01m4tx5nj8emwbyf7y9vcf47y0",
  "entitlements": [
    {
      "featureKey": "seats.max",
      "value": 25
    },
    {
      "featureKey": "feature.export",
      "value": true
    }
  ],
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "id": "prod_01m4tx5nj8emwbyf7y9vcf47y0",
  "object": "product",
  "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
  "livemode": false,
  "name": "Pro",
  "description": "For growing teams.",
  "taxCategoryId": "saas",
  "kind": "plan",
  "entitlements": [
    {
      "featureKey": "seats.max",
      "value": 25
    },
    {
      "featureKey": "feature.export",
      "value": true
    }
  ],
  "lifetimeEntitlements": false,
  "displayOrder": 2,
  "highlighted": true,
  "active": true,
  "images": [],
  "metadata": {},
  "createdAt": "2026-10-13T12:00:00.000Z",
  "updatedAt": "2026-10-14T09:30:00.000Z"
}
```
