# Update customiser length pricing (`update_customiser_length_pricing`)

Changes one length pricing of an advanced_letter or frame_fit customiser with a JSON merge patch: fields you omit stay as they are, shipping merges member by member, and sending rows replaces every boundary row. base_price and price_per_letter are integer minor units of the Store currency; dimensions accept centimetres or inch twins. The patch is judged against the state it would leave, so a change of shipping calculation or letter pricing method that the rows cannot carry is refused. Signs priced by this list use the new prices on the storefront. It requires the pricing:write scope, takes an optional idempotency_key for replay, and supports dry_run.

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

## Purpose

Change the name, letter pricing, shipping settings or boundary rows of one length pricing.

## Annotations

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

## Prerequisites

- A customiser id from list_customisers and a length pricing id from list_customiser_length_pricings.
- The current price list from get_customiser_length_pricing, because rows is replaced whole.
- 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

- Changes what every sign priced by this list costs on the storefront.
- Clears the customiser cached storefront configuration.
- Spends one unit of the store option-write budget.

## Arguments

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `customiser_id` | integer | yes | The customiser id, from list_customisers. |
| `length_pricing_id` | integer | yes | The length pricing id, from list_customiser_length_pricings. |
| `name` | string | no | 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. 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. One of: `material_length`, `fixed_letter`. |
| `shipping` | object | no | The shipping members to change. Members you omit are left as they are. |
| `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 | Every boundary row of the price list. Sending rows replaces the whole set, and an empty list clears it. A row sent without price_per_letter_by_position keeps the schedule it already had; send null to clear one. |
| `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 | no | An optional client-generated key unique to this logical write, such as a UUID. Repeating the call with the same key and the same arguments replays the original result; the route does not require one. |
| `dry_run` | boolean | no | When true, the patch is checked against the state it would leave 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 length pricing as it now stands, with every row. A dry run returns the update 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 length pricing as it now stands, 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 update verdict. |

## Error cases

| Code | Recovery |
| --- | --- |
| `conflicting_units` | A centimetre field and its inch twin disagree. Send one of them. |
| `idempotency_subject_changed` | The price list was deleted after the recorded update. 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 no longer prices by advanced_letter or frame_fit, so its length pricings cannot be changed. |
| `rate_limited` | The store option-write budget is spent. Wait retry_after seconds. |
| `resource_not_found` | No length pricing with that id belongs to that customiser in this store. |
| `stale_revision` | The Store units or the rows changed after they were read. Read them again and rebuild the patch. |
| `unknown_field` | Remove the field named by pointer; it is not part of a length pricing. |
| `validation_failed` | A field is out of range, or a row would lack the dimension limits, divisor or rate the resulting settings need. The pointer and the row code name it. |

## Example: Rename a price list

Request:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "update_customiser_length_pricing",
    "arguments": {
      "customiser_id": 42,
      "length_pricing_id": 9,
      "name": "Trade"
    }
  }
}
```

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": "Trade",
        "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": [],
        "created_at": "2026-08-13T00:00:00Z",
        "updated_at": "2026-08-13T00:05:00Z"
      },
      "idempotent_replay": false
    }
  }
}
```

## Example: Volumetric shipping without a divisor (error)

Request:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "update_customiser_length_pricing",
    "arguments": {
      "customiser_id": 42,
      "length_pricing_id": 9,
      "shipping": {
        "calculation": "volumetric"
      },
      "dry_run": true
    }
  }
}
```

Response:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "isError": true,
    "structuredContent": {
      "error": {
        "code": "validation_failed",
        "title": "Validation failed",
        "detail": "Volumetric shipping divides the boxed volume by a divisor, so shipping.volumetric_divisor is required whenever the calculation is volumetric.",
        "recovery": "Correct every field listed in errors, using its pointer to locate the value and allowed_values where one is given, then call the tool again.",
        "pointer": "/shipping/volumetric_divisor"
      }
    }
  }
}
```
