# Create customiser language (`create_customiser_language`)

Adds a language to a customiser as a draft with one blank translation row per catalogue key. Nothing a shopper sees changes until the required copy is written with update_customiser_language_translations and the language is published with publish_customiser_language. The language code is normalised to its canonical BCP 47 form, and the customiser must not already have it. The new language is never the default. It does not start an AI translation. It requires the customisers:write scope and an idempotency_key; dry_run true checks the create without adding anything.

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

## Purpose

Start a new storefront language for a customiser, which the merchant then translates and publishes.

## Annotations

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

## Prerequisites

- A customiser id from list_customisers.
- A language code the customiser does not have yet; list_customiser_languages shows the ones it has.
- A merchant has connected this store and approved the customisers:write scope.

## Side effects

- Adds one draft language with a blank translation row per catalogue key. Shopper-visible copy does not change.
- Clears the customiser cached storefront configuration and runs the language sync.
- Claims the idempotency_key for at least 24 hours.

## Arguments

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `customiser_id` | integer | yes | The customiser id, from list_customisers. |
| `language_code` | string | yes | The IETF language tag to add, such as fr or fr-CA. It is normalised to its canonical form, so fr_ca creates fr-CA. The customiser must not already have it. |
| `source_language_code` | string | no | The language this one is translated from. It must be a language the customiser already has. Defaults to the authoring language, or to the default language when no authoring language is recorded. |
| `display_name` | string | no | How the language is named in the merchant admin. Defaults to the name of the language in that language. |
| `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 create is checked against the language tag rules and the languages the customiser already has 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 new language record with its translation summary. A dry run returns the create verdict instead and adds nothing.

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `request_id` | string | yes |  |
| `store` | object | yes |  |
| `store.id` | integer | yes |  |
| `store.name` | string | yes |  |
| `customiser_id` | integer | yes |  |
| `language` | object | no | The created language. Absent on a dry run. |
| `language.language_code` | string | yes |  |
| `language.source_language_code` | string or null | no |  |
| `language.display_name` | string or null | no |  |
| `language.status` | string | yes |  |
| `language.is_default` | boolean | yes |  |
| `language.is_authoring` | boolean | yes |  |
| `language.translation_summary` | object or null | no |  |
| `language.ai_translation` | object or null | no |  |
| `language.published_at` | string or null | no |  |
| `language.updated_at` | string or null | no |  |
| `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. |
| `idempotent_replay` | boolean | no | True when this call replayed a previously recorded response for the same idempotency_key instead of applying the write again. |

## Error cases

| Code | Recovery |
| --- | --- |
| `idempotency_key_conflict` | The key was already used with other arguments. Re-send the original arguments or use a new key. |
| `insufficient_scope` | The connection was approved without customisers:write. |
| `resource_not_found` | No customiser with that id exists in this store. |
| `validation_failed` | The language code is not a valid IETF tag, the customiser already has it, or source_language_code names a language the customiser does not have. Read errors for the pointer. |

## Example: Add Canadian French as a draft

Request:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_customiser_language",
    "arguments": {
      "customiser_id": 42,
      "language_code": "fr-CA",
      "display_name": "Canadian French",
      "idempotency_key": "2f1c8a90-6b1e-4c3a-9d7f-8e0a1b2c3d4e"
    }
  }
}
```

Response:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "structuredContent": {
      "request_id": "req_01jz9x2k7c8f3m5n6p7q8r9s0t",
      "store": {
        "id": 12,
        "name": "Demo Signs"
      },
      "customiser_id": 42,
      "language": {
        "language_code": "fr-CA",
        "source_language_code": "en",
        "display_name": "Canadian French",
        "status": "draft",
        "is_default": false,
        "is_authoring": false,
        "translation_summary": {
          "key_count": 184,
          "required_key_count": 96,
          "translated_key_count": 0,
          "retired_key_count": 0,
          "missing_required_key_count": 96,
          "publishable": false
        },
        "ai_translation": null,
        "published_at": null,
        "updated_at": "2026-05-04T09:09:00Z"
      },
      "idempotent_replay": false
    }
  }
}
```

## Example: A language the customiser already has (error)

Request:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_customiser_language",
    "arguments": {
      "customiser_id": 42,
      "language_code": "fr",
      "idempotency_key": "3a2d9b01-7c2f-4d4b-8e80-9f1b2c3d4e5f"
    }
  }
}
```

Response:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "isError": true,
    "structuredContent": {
      "error": {
        "code": "validation_failed",
        "title": "Validation failed",
        "detail": "The request payload failed validation. Correct the fields listed in errors and retry.",
        "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.",
        "errors": [
          {
            "pointer": "/language_code",
            "code": "invalid_value",
            "detail": "This customiser already has a language with that code."
          }
        ]
      }
    }
  }
}
```
