# Promotion codes tools

> MCP tools for Promotion codes: promotion_codes_archive, promotion_codes_create, promotion_codes_get, promotion_codes_list, promotion_codes_update.

- `promotion_codes_archive`: Archive a promotion code
- `promotion_codes_create`: Create a promotion code
- `promotion_codes_get`: Get a promotion code
- `promotion_codes_list`: List promotion codes
- `promotion_codes_update`: Update a promotion code

## `promotion_codes_archive`

**Archive a promotion code.** Operation [`promotion_codes.archive`](https://ownrecord.io/docs/reference/promotion-codes-archive.md) · Brand level · permission `catalog:write` · risk tier `write` · since M5

Archives a promotion code: customers can't use it any more. Discounts already applied with it keep running,
and its code stays taken. Archiving an archived code changes nothing. Reactivate it with
`promotion_codes.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 (`promo_…`) | 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 promotion code with this ID is visible to the request.
- [`version_conflict`](https://ownrecord.io/docs/reference/errors.md#version_conflict): The code 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

**Stop a leaked code**

Arguments:

```json
{
  "id": "promo_01m4tx5nj8emwbyf7y9vcf47z2",
  "reason": "Shared on a coupon site",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "id": "promo_01m4tx5nj8emwbyf7y9vcf47z2",
  "object": "promotion_code",
  "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
  "livemode": false,
  "couponId": "cpn_01m4tx5nj8emwbyf7y9vcf47z0",
  "code": "LAUNCH50",
  "restrictions": {
    "firstTimeOnly": true,
    "minimumAmount": null,
    "customerId": null,
    "expiresAt": null,
    "maxRedemptions": 200
  },
  "timesRedeemed": 0,
  "active": false,
  "metadata": {},
  "createdAt": "2026-10-13T12:00:00.000Z",
  "updatedAt": "2026-10-14T09:30:00.000Z"
}
```

## `promotion_codes_create`

**Create a promotion code.** Operation [`promotion_codes.create`](https://ownrecord.io/docs/reference/promotion-codes-create.md) · Brand level · permission `catalog:write` · risk tier `write` · since M5

Creates a customer-facing code for a coupon, such as `LAUNCH50`. Customers enter it at checkout (any case);
codes are unique in the brand and mode and are never reused, archived ones included.

`restrictions` limit who can use it and when: `firstTimeOnly` (no earlier successful payment or paid invoice),
`minimumAmount` (the purchase before discounts, same currency), `customerId`, `expiresAt` and
`maxRedemptions`. They are checked when the code is applied; redemptions are counted on successful payment.
The code and its restrictions can't change later: archive it and create another.

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

### Arguments

| Argument          | Type         | Required | Description                                                                                                                                                                                              |
| ----------------- | ------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `couponId`        | ID (`cpn_…`) | yes      | The coupon customers get with the code.                                                                                                                                                                  |
| `code`            | string       | yes      | What customers type, e.g. LAUNCH50. Case doesn't matter; unique in the brand and mode.                                                                                                                   |
| `restrictions`    | object       | no       | Who can use the code and when. They can't change later: create a new code instead.                                                                                                                       |
| `active`          | boolean      | no       | Default true; false creates it archived.                                                                                                                                                                 |
| `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

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): The code or a restriction is malformed, or `expiresAt` is in the past.
- [`promotion_code_taken`](https://ownrecord.io/docs/reference/errors.md#promotion_code_taken): The brand already has this code in this mode (in any case).
- [`coupon_not_redeemable`](https://ownrecord.io/docs/reference/errors.md#coupon_not_redeemable): The coupon is archived, expired or used up.
- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): The coupon or the restricted customer isn't in this brand and mode.
- [`customer_deleted`](https://ownrecord.io/docs/reference/errors.md#customer_deleted): The restricted customer was deleted.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): You can't change this brand's catalog.

### Example

**A launch code for first-time customers**

Arguments:

```json
{
  "couponId": "cpn_01m4tx5nj8emwbyf7y9vcf47z0",
  "code": "LAUNCH50",
  "restrictions": {
    "firstTimeOnly": true,
    "maxRedemptions": 200
  },
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "id": "promo_01m4tx5nj8emwbyf7y9vcf47z2",
  "object": "promotion_code",
  "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
  "livemode": false,
  "couponId": "cpn_01m4tx5nj8emwbyf7y9vcf47z0",
  "code": "LAUNCH50",
  "restrictions": {
    "firstTimeOnly": true,
    "minimumAmount": null,
    "customerId": null,
    "expiresAt": null,
    "maxRedemptions": 200
  },
  "timesRedeemed": 0,
  "active": true,
  "metadata": {},
  "createdAt": "2026-10-13T12:00:00.000Z",
  "updatedAt": "2026-10-13T12:00:00.000Z"
}
```

## `promotion_codes_get`

**Get a promotion code.** Operation [`promotion_codes.get`](https://ownrecord.io/docs/reference/promotion-codes-get.md) · Brand level · permission `catalog:read` · risk tier `read` · since M5

Returns one promotion code of the brand in the request's mode, archived or not, with its restrictions and how often it was redeemed. To find a code by what customers type, use `promotion_codes.list` with `code`.

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

### Arguments

| Argument    | Type           | Required | Description                                                                                                                              |
| ----------- | -------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `id`        | ID (`promo_…`) | 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 promotion code 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 launch code**

Arguments:

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

Result `output`:

```json
{
  "id": "promo_01m4tx5nj8emwbyf7y9vcf47z2",
  "object": "promotion_code",
  "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
  "livemode": false,
  "couponId": "cpn_01m4tx5nj8emwbyf7y9vcf47z0",
  "code": "LAUNCH50",
  "restrictions": {
    "firstTimeOnly": true,
    "minimumAmount": null,
    "customerId": null,
    "expiresAt": null,
    "maxRedemptions": 200
  },
  "timesRedeemed": 0,
  "active": true,
  "metadata": {},
  "createdAt": "2026-10-13T12:00:00.000Z",
  "updatedAt": "2026-10-13T12:00:00.000Z"
}
```

## `promotion_codes_list`

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

Lists the brand's promotion codes in the request's mode, newest first. Filter by `couponId`, by `active`, or by
`code` to look a code up the way customers type it (case doesn't matter; at most one result).

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                                                                                                                              |
| ---------------- | -------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `couponId`       | ID (`cpn_…`)   | no       | Only the codes of this coupon.                                                                                                           |
| `code`           | string         | no       | Find the promotion code customers type as this text (case doesn't matter). (1–100 characters)                                            |
| `active`         | string         | no       | Only active (true) or archived (false) codes.                                                                                            |
| `limit`          | integer        | no       | (1–100)                                                                                                                                  |
| `starting_after` | ID (`promo_…`) | 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.

### Examples

**Look up a code as a customer typed it**

Arguments:

```json
{
  "code": "launch50",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
```

Result `output`:

```json
{
  "object": "list",
  "data": [
    {
      "id": "promo_01m4tx5nj8emwbyf7y9vcf47z2",
      "object": "promotion_code",
      "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
      "livemode": false,
      "couponId": "cpn_01m4tx5nj8emwbyf7y9vcf47z0",
      "code": "LAUNCH50",
      "restrictions": {
        "firstTimeOnly": true,
        "minimumAmount": null,
        "customerId": null,
        "expiresAt": null,
        "maxRedemptions": 200
      },
      "timesRedeemed": 0,
      "active": true,
      "metadata": {},
      "createdAt": "2026-10-13T12:00:00.000Z",
      "updatedAt": "2026-10-13T12:00:00.000Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

**A coupon's codes**

Arguments:

```json
{
  "couponId": "cpn_01m4tx5nj8emwbyf7y9vcf47z0",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
```

Result `output`:

```json
{
  "object": "list",
  "data": [
    {
      "id": "promo_01m4tx5nj8emwbyf7y9vcf47z2",
      "object": "promotion_code",
      "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
      "livemode": false,
      "couponId": "cpn_01m4tx5nj8emwbyf7y9vcf47z0",
      "code": "LAUNCH50",
      "restrictions": {
        "firstTimeOnly": true,
        "minimumAmount": null,
        "customerId": null,
        "expiresAt": null,
        "maxRedemptions": 200
      },
      "timesRedeemed": 0,
      "active": true,
      "metadata": {},
      "createdAt": "2026-10-13T12:00:00.000Z",
      "updatedAt": "2026-10-13T12:00:00.000Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

## `promotion_codes_update`

**Update a promotion code.** Operation [`promotion_codes.update`](https://ownrecord.io/docs/reference/promotion-codes-update.md) · Brand level · permission `catalog:write` · risk tier `write` · since M5

Archives (`active: false`) or reactivates (`active: true`) a promotion code, or replaces its metadata. The code
and its restrictions never change: archive the code and create a new one instead. A reactivated code still
needs a redeemable coupon to be used.

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

### Arguments

| Argument          | Type           | Required | Description                                                                                                                                                                                              |
| ----------------- | -------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`              | ID (`promo_…`) | yes      | –                                                                                                                                                                                                        |
| `active`          | boolean        | no       | false archives the code, true reactivates it.                                                                                                                                                            |
| `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

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): Neither `active` nor `metadata` was sent.
- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): No promotion code with this ID is visible to the request.
- [`version_conflict`](https://ownrecord.io/docs/reference/errors.md#version_conflict): The code changed at the same time (for example a redemption); retry.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): You can't change this brand's catalog.

### Example

**Pause a code**

Arguments:

```json
{
  "id": "promo_01m4tx5nj8emwbyf7y9vcf47z2",
  "active": false,
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "id": "promo_01m4tx5nj8emwbyf7y9vcf47z2",
  "object": "promotion_code",
  "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
  "livemode": false,
  "couponId": "cpn_01m4tx5nj8emwbyf7y9vcf47z0",
  "code": "LAUNCH50",
  "restrictions": {
    "firstTimeOnly": true,
    "minimumAmount": null,
    "customerId": null,
    "expiresAt": null,
    "maxRedemptions": 200
  },
  "timesRedeemed": 0,
  "active": false,
  "metadata": {},
  "createdAt": "2026-10-13T12:00:00.000Z",
  "updatedAt": "2026-10-14T09:30:00.000Z"
}
```
