# Create customiser length pricing (`create_customiser_length_pricing`)

Creates one length pricing on an advanced_letter or frame_fit customiser: a named price list of boundary rows by sign size. Only name is required. Each row needs base_price in integer minor units of the Store currency, the dimension limits its shipping calculation needs, and outside frame_fit the rate its letter pricing method charges by; dimensions accept centimetres or inch twins. A frame_fit customiser holds exactly one price list, which prices every sign, so a second create is refused. Another pricing model is refused with pricing_model_mismatch. It requires the pricing:write scope and an idempotency_key, and supports dry_run.

- Source URL: https://www.signcustomiser.com/help/mcp/tools/create_customiser_length_pricing/
- Markdown URL: https://www.signcustomiser.com/help/mcp/tools/create_customiser_length_pricing.md
- MCP endpoint: https://mcp.signcustomiser.com
- Required scope: `pricing:write`
- Behaviour: destructive write

## Purpose

Add a price list that an advanced_letter or frame_fit customiser can charge signs by.

## Annotations

| Hint | Value |
| --- | --- |
| readOnlyHint | false |
| destructiveHint | true |
| idempotentHint | true |
| openWorldHint | false |

## Prerequisites

- An advanced_letter or frame_fit customiser id from list_customisers.
- The Store unit_revision from get_store when sending inch twins.
- A merchant has connected this store and approved the pricing:write scope.

## Side effects

- Creates one length pricing on one customiser. On a frame_fit customiser it becomes the price list every sign is priced by.
- Clears the customiser cached storefront configuration.
- Spends one unit of the store option-write budget.
- Claims the idempotency_key for at least 24 hours.

## Arguments

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `customiser_id` | integer | yes | The customiser id, from list_customisers. |
| `name` | string | yes | The price list display name. |
| `letter_pricing_mode` | string | no | How letters are charged (closed set): every_letter_cost charges each letter the same, price_by_position charges by letter position from the per-position schedule. Defaults to every_letter_cost. One of: `every_letter_cost`, `price_by_position`. |
| `letter_pricing_method` | string | no | What each row charges letters by (closed set): material_length charges price_per_cm of material, fixed_letter charges price_per_letter. Defaults to fixed_letter. One of: `material_length`, `fixed_letter`. |
| `shipping` | object | no | How the shipping dimensions of the finished sign are derived. Defaults to physical with add_weight_to_product false. |
| `shipping.calculation` | string | no | physical measures the sign; volumetric measures the box and needs volumetric_divisor (closed set). One of: `physical`, `volumetric`. |
| `shipping.volumetric_divisor` | integer or null | no | The divisor the boxed volume is divided by. Required when calculation is volumetric. |
| `shipping.volumetric_divisor_authored` | integer or null | no | The divisor as the merchant authored it, in volumetric_divisor_unit. |
| `shipping.volumetric_divisor_unit` | string or null | no | The unit volumetric_divisor_authored is in. One of: `cm3_per_kg`, `in3_per_kg`, `cm3_per_lb`, `in3_per_lb`. |
| `shipping.volumetric_divisor_canonical` | number or null | no | The divisor converted to cubic centimetres per kilogram. Must be greater than 0. |
| `shipping.authored_units` | object | no | The unit the merchant authored the divisor in. |
| `shipping.authored_units.volumetric_divisor` | string | no | One of: `cm3_per_kg`, `in3_per_kg`, `cm3_per_lb`, `in3_per_lb`. |
| `shipping.add_weight_to_product` | boolean | no | Whether the calculated weight is written onto the created product. |
| `unit_revision` | integer | no | The Store unit revision from get_store. The write is refused with stale_revision if the Store unit preferences changed after it was read. |
| `rows_revision` | integer | no | The rows_revision from the last read of this price list. The write is refused with stale_revision if the rows changed after that read. |
| `rows` | array of object | no | One boundary row per size band. Defaults to an empty list. |
| `rows[].id` | string | no | The stable row id from a previous read. Send it back to keep the row identity. |
| `rows[].base_price` | integer | yes | The flat amount a sign in this band costs, in integer minor units of the Store currency. |
| `rows[].price_per_letter` | integer or null | no | The amount each letter adds, in integer minor units. Required on a fixed_letter list outside frame_fit. |
| `rows[].price_per_letter_by_position` | array of integer | no | Only on a price_by_position fixed_letter list: 1 to 100 per-letter prices in integer minor units, the first entry being the first letter. |
| `rows[].price_per_cm` | number or null | no | The amount each centimetre of material adds, in decimal major currency units. Required on a material_length list outside frame_fit. |
| `rows[].price_per_in` | number or null | no | The inch twin of price_per_cm, in decimal major currency units per inch. |
| `rows[].price_per_sq_cm` | number or null | no | A decimal major-currency rate: per square centimetre for physical shipping, per kilogram of volumetric weight for volumetric shipping. |
| `rows[].price_per_sq_in` | number or null | no | The square-inch twin of price_per_sq_cm. |
| `rows[].price_per_lb` | number or null | no | The per-pound twin of price_per_sq_cm on a volumetric list. |
| `rows[].price_per_sq_cm_basis` | string | no | Whether price_per_sq_cm applies to physical area or volumetric weight (closed set). One of: `per_sq_cm`, `per_kg_volumetric`. |
| `rows[].max_price_multiplier` | number or null | no | The ceiling the row computed price is capped at, as a multiple. |
| `rows[].width_limit_cm` | number or null | no | The widest sign this row covers, in centimetres. |
| `rows[].width_limit_in` | number or null | no | The inch twin of width_limit_cm. |
| `rows[].height_limit_cm` | number or null | no | The tallest sign this row covers, in centimetres. |
| `rows[].height_limit_in` | number or null | no | The inch twin of height_limit_cm. |
| `rows[].length_limit_cm` | number or null | no | The depth boundary this row covers, in centimetres. |
| `rows[].length_limit_in` | number or null | no | The inch twin of length_limit_cm. |
| `rows[].fixed_length_cm` | number or null | no | The fixed sign depth in centimetres for non-Lightbox volumetric shipping. |
| `rows[].fixed_length_in` | number or null | no | The inch twin of fixed_length_cm. |
| `rows[].width_modifier` | number or null | no | Adjusts the measured width before pricing, in centimetres. |
| `rows[].width_modifier_in` | number or null | no | The inch twin of width_modifier. |
| `rows[].height_modifier` | number or null | no | Adjusts the measured height before pricing, in centimetres. |
| `rows[].height_modifier_in` | number or null | no | The inch twin of height_modifier. |
| `rows[].length_modifier` | number or null | no | Lightbox-only extra parcel depth added for packaging, in centimetres. |
| `rows[].length_modifier_in` | number or null | no | The inch twin of length_modifier. |
| `rows[].authored_units` | object | no | The unit the merchant authored each quantity in, keyed by its centimetre field name. |
| `rows[].authored_units.width_limit_cm` | string | no | One of: `centimetres`, `inches`. |
| `rows[].authored_units.height_limit_cm` | string | no | One of: `centimetres`, `inches`. |
| `rows[].authored_units.length_limit_cm` | string | no | One of: `centimetres`, `inches`. |
| `rows[].authored_units.fixed_length_cm` | string | no | One of: `centimetres`, `inches`. |
| `rows[].authored_units.width_modifier` | string | no | One of: `centimetres`, `inches`. |
| `rows[].authored_units.height_modifier` | string | no | One of: `centimetres`, `inches`. |
| `rows[].authored_units.length_modifier` | string | no | One of: `centimetres`, `inches`. |
| `rows[].authored_units.price_per_cm` | string | no | One of: `centimetres`, `inches`. |
| `rows[].authored_units.price_per_sq_cm` | string | no | One of: `centimetres`, `inches`, `kilograms`, `pounds`. |
| `idempotency_key` | string | unless `dry_run` is true | A client-generated key unique to this logical write, such as a UUID. Required: repeating the call with the same key and the same arguments replays the original result instead of writing twice, and the same key with different arguments is an idempotency_key_conflict. |
| `dry_run` | boolean | no | When true, the price list is checked against the customiser pricing model and the shipping calculation is checked and nothing is written: the result is the same verdict the apply would have reached, and a problem the apply would have raised comes back as the same tool error. Defaults to false. A dry run claims no key. |

## Result

The created length pricing with every row, in the shape get_customiser_length_pricing returns. A dry run returns the create verdict instead and writes nothing.

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `request_id` | string | yes |  |
| `store` | object | yes |  |
| `store.id` | integer | yes |  |
| `store.name` | string | yes |  |
| `customiser_id` | integer | yes |  |
| `length_pricing` | object | no | The created length pricing with every row. Absent on a dry run. |
| `length_pricing.id` | integer | yes |  |
| `length_pricing.customiser_id` | integer | yes |  |
| `length_pricing.name` | string or null | no |  |
| `length_pricing.letter_pricing_mode` | string or null | no | One of: `every_letter_cost`, `price_by_position`. |
| `length_pricing.letter_pricing_method` | string or null | no | One of: `material_length`, `fixed_letter`. |
| `length_pricing.shipping` | object | no |  |
| `length_pricing.shipping.calculation` | string or null | no |  |
| `length_pricing.shipping.volumetric_divisor` | integer or null | no |  |
| `length_pricing.shipping.volumetric_divisor_authored` | integer or null | no |  |
| `length_pricing.shipping.volumetric_divisor_unit` | string or null | no |  |
| `length_pricing.shipping.volumetric_divisor_canonical` | number or null | no |  |
| `length_pricing.shipping.authored_units` | object | no |  |
| `length_pricing.shipping.add_weight_to_product` | boolean | no |  |
| `length_pricing.currency` | string or null | no | The ISO 4217 currency of every integer minor-unit amount on the record. |
| `length_pricing.rows_revision` | integer | no |  |
| `length_pricing.rows` | array of object | no |  |
| `length_pricing.rows[].id` | string | no |  |
| `length_pricing.rows[].base_price` | number or null | no |  |
| `length_pricing.rows[].price_per_letter` | number or null | no |  |
| `length_pricing.rows[].price_per_cm` | number or null | no |  |
| `length_pricing.rows[].price_per_in` | number or null | no |  |
| `length_pricing.rows[].price_per_sq_cm` | number or null | no |  |
| `length_pricing.rows[].price_per_sq_in` | number or null | no |  |
| `length_pricing.rows[].price_per_lb` | number or null | no |  |
| `length_pricing.rows[].max_price_multiplier` | number or null | no |  |
| `length_pricing.rows[].width_limit_cm` | number or null | no |  |
| `length_pricing.rows[].width_limit_in` | number or null | no |  |
| `length_pricing.rows[].height_limit_cm` | number or null | no |  |
| `length_pricing.rows[].height_limit_in` | number or null | no |  |
| `length_pricing.rows[].length_limit_cm` | number or null | no |  |
| `length_pricing.rows[].length_limit_in` | number or null | no |  |
| `length_pricing.rows[].fixed_length_cm` | number or null | no |  |
| `length_pricing.rows[].fixed_length_in` | number or null | no |  |
| `length_pricing.rows[].width_modifier` | number or null | no |  |
| `length_pricing.rows[].width_modifier_in` | number or null | no |  |
| `length_pricing.rows[].height_modifier` | number or null | no |  |
| `length_pricing.rows[].height_modifier_in` | number or null | no |  |
| `length_pricing.rows[].length_modifier` | number or null | no |  |
| `length_pricing.rows[].length_modifier_in` | number or null | no |  |
| `length_pricing.rows[].price_per_sq_cm_basis` | string | no | One of: `per_sq_cm`, `per_kg_volumetric`. |
| `length_pricing.rows[].price_per_letter_by_position` | array of integer | no |  |
| `length_pricing.rows[].authored_units` | object | no |  |
| `length_pricing.created_at` | string or null | no |  |
| `length_pricing.updated_at` | string or null | no |  |
| `idempotent_replay` | boolean | no | True when this call replayed a previously recorded response for the same idempotency_key instead of applying the write again. |
| `dry_run` | boolean | no | Present and true only when dry_run was requested. |
| `valid` | boolean | no | Present only on a dry run, and always true: a failing dry run returns a tool error instead. |
| `verdict` | object | no | Present only on a dry run: the create verdict. |

## Error cases

| Code | Recovery |
| --- | --- |
| `conflicting_units` | A centimetre field and its inch twin disagree. Send one of them. |
| `idempotency_subject_changed` | The created price list was later deleted. Issue a new key and re-check the resource before writing again. |
| `insufficient_scope` | The connection was approved without pricing:write. |
| `pricing_model_mismatch` | The customiser does not price by advanced_letter or frame_fit, so a length pricing would never be used. |
| `rate_limited` | The store option-write budget is spent. Wait retry_after seconds. |
| `resource_not_found` | No customiser with that id belongs to this store. |
| `stale_revision` | The Store units changed after unit_revision was read. Call get_store and rebuild the write. |
| `unknown_field` | Remove the field named by pointer; it is not part of a length pricing. |
| `validation_failed` | A field is missing or out of range, a row lacks the dimension limits or rate it needs, or a frame_fit customiser already has its one price list. The pointer and the row code name it. |

## Example: Add a fixed-letter price list with one band

Request:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_customiser_length_pricing",
    "arguments": {
      "customiser_id": 42,
      "name": "Standard",
      "rows": [
        {
          "base_price": 4500,
          "width_limit_cm": 120,
          "height_limit_cm": 60,
          "price_per_letter": 600
        }
      ],
      "idempotency_key": "7c1d2e3f-4a5b-4c6d-8e9f-0a1b2c3d4e5f"
    }
  }
}
```

Response:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "structuredContent": {
      "request_id": "req_01jz9x2k7c8f3m5n6p7q8r9s0t",
      "store": {
        "id": 12,
        "name": "Demo Signs"
      },
      "customiser_id": 42,
      "length_pricing": {
        "id": 9,
        "customiser_id": 42,
        "name": "Standard",
        "letter_pricing_mode": "every_letter_cost",
        "letter_pricing_method": "fixed_letter",
        "shipping": {
          "calculation": "physical",
          "volumetric_divisor": null,
          "add_weight_to_product": false
        },
        "currency": "USD",
        "rows": [
          {
            "base_price": 4500,
            "width_limit_cm": 120,
            "height_limit_cm": 60,
            "price_per_letter": 600
          }
        ],
        "created_at": "2026-08-13T00:00:00Z",
        "updated_at": "2026-08-13T00:00:00Z"
      },
      "idempotent_replay": false
    }
  }
}
```

## Example: A customiser that does not price by length (error)

Request:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_customiser_length_pricing",
    "arguments": {
      "customiser_id": 43,
      "name": "Standard",
      "dry_run": true
    }
  }
}
```

Response:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "isError": true,
    "structuredContent": {
      "error": {
        "code": "pricing_model_mismatch",
        "title": "Pricing model mismatch",
        "detail": "A length pricing is used only by a customiser that prices by advanced_letter or frame_fit, and this customiser prices by simple_letter. Change the customiser's pricing model first, or write the price list it does use.",
        "recovery": "The fields sent do not belong to this customiser pricing model. Read the customiser to see its pricing_model and send only the fields that model accepts."
      }
    }
  }
}
```
