# Create customiser font tier (`create_customiser_font_tier`)

Creates one font tier on a simple_letter customiser: a named price list that fonts are charged by. Only name is required. Each row names one of the customiser sizes and carries base_price and letter_price maps keyed line_1 to line_10 in integer minor units of the Store currency, with line_1 to line_3 required. A customiser on another pricing model is refused with pricing_model_mismatch. The pricing document publishes the same tiers as price_lists, and its replace deletes any tier it omits. 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_font_tier/
- Markdown URL: https://www.signcustomiser.com/help/mcp/tools/create_customiser_font_tier.md
- MCP endpoint: https://mcp.signcustomiser.com
- Required scope: `pricing:write`
- Behaviour: destructive write

## Purpose

Add a price list that a simple_letter customiser can charge its fonts by.

## Annotations

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

## Prerequisites

- A simple_letter customiser id from list_customisers.
- The customiser size ids, from list_customiser_options with option_type size, for any rows.
- A merchant has connected this store and approved the pricing:write scope.

## Side effects

- Creates one font tier on one customiser.
- 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 tier 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`. |
| `rows` | array of object | no | One priced row per customiser size. Defaults to an empty list. |
| `rows[].size_id` | integer | yes | One of the customiser own size ids, from list_customiser_options with option_type size. |
| `rows[].base_price` | object | yes | The flat amount a sign costs at this size, keyed line_1 to line_10 by line count, in integer minor units of the Store currency. line_1 to line_3 are required. |
| `rows[].base_price.line_1` | integer | yes |  |
| `rows[].base_price.line_2` | integer | yes |  |
| `rows[].base_price.line_3` | integer | yes |  |
| `rows[].base_price.line_4` | integer or null | no |  |
| `rows[].base_price.line_5` | integer or null | no |  |
| `rows[].base_price.line_6` | integer or null | no |  |
| `rows[].base_price.line_7` | integer or null | no |  |
| `rows[].base_price.line_8` | integer or null | no |  |
| `rows[].base_price.line_9` | integer or null | no |  |
| `rows[].base_price.line_10` | integer or null | no |  |
| `rows[].letter_price` | object | yes | The amount each letter adds at this size, keyed line_1 to line_10 by line count, in integer minor units of the Store currency. line_1 to line_3 are required. |
| `rows[].letter_price.line_1` | integer | yes |  |
| `rows[].letter_price.line_2` | integer | yes |  |
| `rows[].letter_price.line_3` | integer | yes |  |
| `rows[].letter_price.line_4` | integer or null | no |  |
| `rows[].letter_price.line_5` | integer or null | no |  |
| `rows[].letter_price.line_6` | integer or null | no |  |
| `rows[].letter_price.line_7` | integer or null | no |  |
| `rows[].letter_price.line_8` | integer or null | no |  |
| `rows[].letter_price.line_9` | integer or null | no |  |
| `rows[].letter_price.line_10` | integer or null | no |  |
| `rows[].letter_price_by_position` | object or null | no | Only on a price_by_position tier: per line count, a list of 1 to 100 per-letter prices in integer minor units, the first entry being the first letter. |
| `rows[].letter_price_by_position.line_1` | array of integer | no |  |
| `rows[].letter_price_by_position.line_2` | array of integer | no |  |
| `rows[].letter_price_by_position.line_3` | array of integer | no |  |
| `rows[].letter_price_by_position.line_4` | array of integer | no |  |
| `rows[].letter_price_by_position.line_5` | array of integer | no |  |
| `rows[].letter_price_by_position.line_6` | array of integer | no |  |
| `rows[].letter_price_by_position.line_7` | array of integer | no |  |
| `rows[].letter_price_by_position.line_8` | array of integer | no |  |
| `rows[].letter_price_by_position.line_9` | array of integer | no |  |
| `rows[].letter_price_by_position.line_10` | array of integer | no |  |
| `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 tier is checked against the customiser pricing model and its sizes 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 font tier with every row, in the shape get_customiser_font_tier 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 |  |
| `font_tier` | object | no | The created font tier with every row. Absent on a dry run. |
| `font_tier.id` | integer | yes |  |
| `font_tier.customiser_id` | integer | yes |  |
| `font_tier.name` | string or null | no |  |
| `font_tier.letter_pricing_mode` | string or null | no | One of: `every_letter_cost`, `price_by_position`. |
| `font_tier.currency` | string or null | no | The ISO 4217 currency of every integer minor-unit amount on the record. |
| `font_tier.rows` | array of object | no |  |
| `font_tier.rows[].size_id` | integer or null | no |  |
| `font_tier.rows[].base_price` | object | no |  |
| `font_tier.rows[].letter_price` | object | no |  |
| `font_tier.rows[].letter_price_by_position` | object or null | no |  |
| `font_tier.created_at` | string or null | no |  |
| `font_tier.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 |
| --- | --- |
| `idempotency_subject_changed` | The created tier 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 simple_letter, so a font tier 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. |
| `unknown_field` | Remove the field named by pointer; it is not part of a font tier. |
| `validation_failed` | A field is missing or out of range, or a row names a size that is not this customiser. The pointer names it. |

## Example: Add a tier with one size row

Request:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_customiser_font_tier",
    "arguments": {
      "customiser_id": 42,
      "name": "Standard",
      "rows": [
        {
          "size_id": 3,
          "base_price": {
            "line_1": 2500,
            "line_2": 3000,
            "line_3": 3500
          },
          "letter_price": {
            "line_1": 400,
            "line_2": 450,
            "line_3": 500
          }
        }
      ],
      "idempotency_key": "5b1a9f1e-3a4d-4b0b-9a6e-6a9b0f0f1a2b"
    }
  }
}
```

Response:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "structuredContent": {
      "request_id": "req_01jz9x2k7c8f3m5n6p7q8r9s0t",
      "store": {
        "id": 12,
        "name": "Demo Signs"
      },
      "customiser_id": 42,
      "font_tier": {
        "id": 7,
        "customiser_id": 42,
        "name": "Standard",
        "letter_pricing_mode": "every_letter_cost",
        "currency": "USD",
        "rows": [
          {
            "size_id": 3,
            "base_price": {
              "line_1": 2500,
              "line_2": 3000,
              "line_3": 3500
            },
            "letter_price": {
              "line_1": 400,
              "line_2": 450,
              "line_3": 500
            },
            "letter_price_by_position": null
          }
        ],
        "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 font tier (error)

Request:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_customiser_font_tier",
    "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 font tier is used only by a customiser that prices by simple_letter or legacy_fixed_height, and this customiser prices by advanced_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."
      }
    }
  }
}
```
