> ## Documentation Index
> Fetch the complete documentation index at: https://docs.canary.effectiveai.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Input contract

> The input contract is the JSON Schema of a rater's request, with the keys a host reads to build a form or a conversation.

The compiler writes the contract from the `inputs` blocks of a rater. Each type, rule and annotation becomes one key. A **host** is the application that collects the answers: a form, a conversation or an agent.

The contract follows JSON Schema draft 2020-12. The keys that start with `x-` are additions, and a JSON Schema validator ignores them. The rater's own check of a request is the authority: the contract publishes each rule that JSON Schema has a keyword for, and lists the other rules as text.

[Ask the questions a rater needs](https://docs.canary.effectiveai.app/rsl/tutorial/ask-questions.md) writes the annotations that this page reads back.

## Get the contract

Save the `questions` example and package it:

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc examples get questions
rslc export questions/rater.rsl --format bundle --out questions.zip
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
WROTE questions/rater.rsl
WROTE bundle: questions.zip
```

The same contract is available in three places:

| Place | Form |
| - | - |
| `program/input-schema.json` in the bundle | Compact JSON with sorted keys, written once at export |
| `Rater.load("questions.zip").input_schema` | A dictionary, copied on each read |
| `Rater.build("questions/rater.rsl").input_schema` | The same dictionary, compiled from the source |

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
import json
import zipfile

from relativities import Rater

contract = Rater.load("questions.zip").input_schema
list(contract)
# ['$defs', '$schema', 'additionalProperties', 'properties', 'required', 'type']

with zipfile.ZipFile("questions.zip") as bundle:
    packed = json.loads(bundle.read("program/input-schema.json"))
packed == contract
# True
```

`Rater.inputs` shows the same contract as a table, with one row for each field.

## Examples on this page

Most fragments on this page come from the `questions` example, a renters rater. [Examples](https://docs.canary.effectiveai.app/rsl/reference/examples.md) shows its source.

> [accordion] The contract of the questions example
>
> ```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> {
>   "$defs": {
>     "County": { "enum": ["maricopa", "pima", "other"], "type": "string" },
>     "ItemKind": { "enum": ["jewelry", "bicycle", "instrument"], "type": "string" },
>     "Zip": { "description": "Code `Zip`", "type": "string" }
>   },
>   "$schema": "https://json-schema.org/draft/2020-12/schema",
>   "additionalProperties": false,
>   "properties": {
>     "Policy": {
>       "additionalProperties": false,
>       "properties": {
>         "business_property": {
>           "anyOf": [{ "maximum": 20000.0, "minimum": 0.0, "type": "integer" }, { "type": "null" }],
>           "format": "money",
>           "x-ask": "What is the business equipment worth?",
>           "x-group": "Business",
>           "x-rsl-when": "self.home_business",
>           "x-unit": "USD"
>         },
>         "contents_limit": {
>           "default": 30000,
>           "maximum": 100000.0,
>           "minimum": 20000.0,
>           "multipleOf": 1000,
>           "type": "number",
>           "x-ask": "How much contents coverage do you want?",
>           "x-choice": true,
>           "x-group": "liability",
>           "x-round": "up",
>           "x-shape": "limit",
>           "x-unit": "USD"
>         },
>         "county": {
>           "$ref": "#/$defs/County",
>           "x-ask": "Which county is the home in?",
>           "x-from": { "reads": ["zip_county"], "text": "self.zip_county" },
>           "x-group": "Home"
>         },
>         "customers_visit": {
>           "anyOf": [{ "type": "boolean" }, { "type": "null" }],
>           "x-ask": "Do customers visit the home?",
>           "x-group": "Business",
>           "x-rsl-when": "self.home_business"
>         },
>         "home_business": {
>           "type": "boolean",
>           "x-ask": "Do you run a business from the home?",
>           "x-group": "Business",
>           "x-help": "Count paid work you do at home for your own customers."
>         },
>         "items": {
>           "default": [],
>           "items": {
>             "additionalProperties": false,
>             "properties": {
>               "item_id": { "maximum": 99.0, "minimum": 1.0, "type": "integer" },
>               "kind": {
>                 "$ref": "#/$defs/ItemKind",
>                 "x-ask": "What kind of item is item {#}?",
>                 "x-labels": { "instrument": "musical instrument" }
>               },
>               "value": {
>                 "format": "money",
>                 "maximum": 25000.0,
>                 "minimum": 1000.0,
>                 "type": "number",
>                 "x-ask": "What is {this} worth?",
>                 "x-rsl-precision": "0.01",
>                 "x-unit": "USD"
>               }
>             },
>             "required": ["item_id", "kind", "value"],
>             "type": "object",
>             "x-order": ["item_id", "kind", "value"]
>           },
>           "maxItems": 5,
>           "type": ["array", "null"],
>           "x-ask": "Which valuables do you want to schedule?",
>           "x-complete": "Any other valuables?",
>           "x-group": "Valuables",
>           "x-help": "Add each item worth $1,000 or more.",
>           "x-keyed-by": "item_id",
>           "x-label": "the {kind}|item {#}"
>         },
>         "postal_code": {
>           "anyOf": [{ "$ref": "#/$defs/Zip" }, { "type": "null" }],
>           "format": "postal-code",
>           "x-ask": "What is the ZIP code of the home you rent?",
>           "x-group": "Home"
>         },
>         "year_built": {
>           "format": "year",
>           "maximum": 2100.0,
>           "minimum": 1800.0,
>           "type": "integer",
>           "x-ask": "What year was the building built?",
>           "x-group": "Home",
>           "x-help": "The lease or the landlord states the year."
>         },
>         "zip_county": {
>           "anyOf": [{ "$ref": "#/$defs/County" }, { "type": "null" }],
>           "x-filled-by": { "field": "county", "from": "postal_code", "source": "zip_lookup" }
>         }
>       },
>       "required": ["county", "year_built", "home_business"],
>       "type": "object",
>       "x-order": [
>         "postal_code",
>         "zip_county",
>         "county",
>         "year_built",
>         "home_business",
>         "business_property",
>         "customers_visit",
>         "contents_limit",
>         "items"
>       ],
>       "x-rsl-refinements": [
>         {
>           "constraint": "where `business_property` is given when self.home_business"
>         },
>         {
>           "constraint": "where `customers_visit` is given when self.home_business"
>         }
>       ]
>     }
>   },
>   "required": ["Policy"],
>   "type": "object"
> }
> ```

The other fragments come from `household`, a personal auto rater that imports a shared-facts package. A package is a file of inputs that several raters ask in common.

> [accordion] household/facts.rsl, the package
>
> ```rsl household/facts.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> package example.auto version 1
>
> Types = {
>     VinPattern = Code(pattern: "[0-9A-Z]{8}0[0-9A-Z]"),
>     Age = Num {precision: int} where self in [14, 99],
>     Relationship = {
>         applicant [label: "you"],
>         spouse [label: "your spouse"],
>         other [label: "another driver"]
>     },
>     Make [listed] = Enum,
>     Coverage [choices] = {
>         bodily_injury [label: "Bodily injury liability", shape: "limit_pair"]
>     }
> }
>
> Records = {
>     Policy = {
>         inputs {
>             currently_insured: Bool [gives: "declarations_page", ask: "Are your cars insured now?", group: "Policy"],
>             cars: List(Car) keyed by car_id [found_on: "declarations_page", ask: "Which cars do you want to insure?", group: "Cars", called: "the {year} {make}|car {#}", complete: "Any other cars?"]
>                 where len(self) in [1, _],
>             drivers: List(Driver) keyed by driver_id [inline, ask: "Who drives these cars?", group: "Drivers", called: "{relationship}|driver {#}", complete: "Anyone else?", you: "relationship = applicant"]
>                 where len(self) in [1, _]
>         }
>     },
>     Car = {
>         inputs {
>             car_id: Num {precision: int} where self in [1, 9],
>             year: Num {precision: int} [found_on: "declarations_page, registration", ask: "What year is car {#}?", format: "year"]
>                 where self in [1900, 2100],
>             make: Make refines year [closed, ask: "What make is it?"],
>             vin_known: Bool [ask: "Do you have the VIN of {this}?"],
>             vin_pattern: VinPattern when self.vin_known [none_answer: "I don't have it", ask: "What is the VIN of {this}?", format: "vin", reduce: "1-8,'0',10", check: "at 9 mod 11 ten X weights 8 7 6 5 4 3 2 10 0 9 8 7 6 5 4 3 2 values A1 B2 C3 D4 E5 F6 G7 H8 J1 K2 L3 M4 N5 P7 R9 S2 T3 U4 V5 W6 X7 Y8 Z9"],
>             rideshare: Bool [checklist: "Cars", statement: "Used for rideshare or delivery", ask: "Is {this} used for rideshare or delivery?"],
>             garaged: Bool [rows, ask: "Is {this} parked in a garage overnight?"],
>             principal_driver: Num {precision: int} [ask: "Who drives {this} most?", refers: "Policy.drivers", labels: "0=No one more than the others"]
>                 where self in [0, 9]
>         }
>     },
>     Driver = {
>         inputs {
>             driver_id: Num {precision: int} where self in [1, 9],
>             relationship: Relationship [ask: "How is driver {#} related to the policyholder?"],
>             age: Age [ask: "How old is {this}?", ask_you: "How old are you?"],
>             accidents: List(Accident) keyed by accident_id [ask: "Any accidents for {this} since {look_back}?", ask_you: "Any accidents for you since {look_back}?", called: "accident {#}", look_back: 5]
>         }
>     },
>     Accident = {
>         inputs {
>             accident_id: Num {precision: int} where self in [1, 9]
>         }
>     }
> }
> ```
>
> The `Make` enum reads its members from a CSV file:
>
> ```csv household/enums/Make.csv theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> ident,value,label,aliases
> chevrolet,chevrolet,Chevrolet,Chevy;Chev
> toyota,toyota,Toyota,
> ```

> [accordion] household/rater.rsl, the rater
>
> ```rsl household/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Personal Auto (contract example)",
>     effective_date: 2026-01-01
> }
>
> import "facts.rsl"
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     AgeFrom16 = Num {precision: int} where self in [16, 99]
> }
>
> Constants = {
>     car_rate [cite: manual:p2] = 400,
>     vin_credit [cite: manual:p2] = 15,
>     garage_credit [cite: manual:p2] = 25,
>     rideshare_charge [cite: manual:p2] = 200,
>     accident_charge [cite: manual:p3] = 50,
>     paper_fee [cite: manual:p4] = 10
> }
>
> Records = {
>     Policy = {
>         inputs {
>             cars where len(self) in [0, 4] [code: "CAR-COUNT", message: "A policy insures up to four cars."],
>             bodily_injury: Limits [choice, coverage: "bodily_injury", shape: "limit_pair", steps: "25000/50000, 100000/300000", api_default: "{\"per_person\": 25000, \"per_accident\": 50000}"],
>             paperless: Bool [ask: "Do you want your documents by email?", why: "Paperless policies skip the paper fee."]
>         },
>         step rating {
>             fee: Premium [cite: manual:p4] = if self.paperless then 0 else paper_fee,
>             limit_charge: Premium [cite: manual:p4] =
>                 (self.bodily_injury.per_person + self.bodily_injury.per_accident) / 1000,
>             premium: Premium [cite: manual:p5] =
>                 round(sum(self.cars as c, c.premium) + sum(self.drivers as d, d.surcharge) + self.limit_charge + self.fee, 0, half_up)
>         }
>     },
>     Car = {
>         step rating {
>             vin_discount: Premium [cite: manual:p2] =
>                 match self.vin_pattern { Some(v) => vin_credit, None => 0 },
>             garage_discount: Premium [cite: manual:p2] = if self.garaged then garage_credit else 0,
>             rideshare_load: Premium [cite: manual:p2] = if self.rideshare then rideshare_charge else 0,
>             premium: Premium [cite: manual:p2] =
>                 car_rate - self.vin_discount - self.garage_discount + self.rideshare_load
>         }
>     },
>     Driver = {
>         inputs {
>             age as AgeFrom16 [code: "TOO-YOUNG", message: "Drivers are 16 or older."],
>             months_licensed: Num {precision: int} refines age when self.age < 18 [ask: "How many months has {this} held a license?", ask_you: "How many months have you held a license?", why: "New drivers are rated by the month."]
>                 where self in [0, 48]
>         },
>         step rating {
>             new_driver: Premium [cite: manual:p3] = if self.age < 18 then 100 - self.months_licensed else 0,
>             surcharge: Premium [cite: manual:p3] = self.new_driver + accident_charge * len(self.accidents)
>         }
>     },
>     Limits = {
>         inputs {
>             per_person: Num {precision: int} where self in [0, 1000000],
>             per_accident: Num {precision: int} where self in [0, 1000000]
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check household/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
PASS household/rater.rsl (strict)
cost premium: 3 + 5*|Policy.cars| + 3*|Policy.drivers| (each list of size 1: 11, 3: 27, 10: 83)
```

`Rater.build("household/rater.rsl").input_schema` returns its contract.

A third rater, `host`, carries the annotations that only tell a host how to show a question.

> [accordion] host/rater.rsl
>
> ```rsl host/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Renters (host keys example)",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     Heating = {
>         gas [label: "Gas", help: "Natural gas or propane"],
>         electric [label: "Electric"]
>     }
> }
>
> Constants = {
>     base [cite: manual:p2] = 200,
>     gas_charge [cite: manual:p2] = 15,
>     tenure_credit [cite: manual:p2] = 10,
>     roommate_charge [cite: manual:p3] = 30,
>     smoker_charge [cite: manual:p3] = 20
> }
>
> Records = {
>     Policy = {
>         inputs {
>             start: Date [priced_on, earliest: "today", latest: "today + 1 year", short: "Start", ask: "When does the policy start?"]
>                 where self in [2026-01-01, 2027-12-31],
>             moved_in: Date [date_precision: "month", latest: "start", ask: "When did you move in?"]
>                 where self in [1950-01-01, 2027-12-31],
>             heating: Heating [picker: "icon", ask: "How is the home heated?"],
>             complete_list: Bool [attestation, ask: "Does the list name everyone who lives with you?"]
>                 where self [code: "INCOMPLETE", message: "List everyone who lives with you.", cite: manual:p3],
>             roommates: List(Roommate) keyed by roommate_id [add: "Add a roommate", item_name: "First name", ask: "Who lives with you?", called: "roommate {#}", complete: "Anyone else?"]
>         },
>         step rating [cite: manual:p2] {
>             heat: Num = match self.heating { @Heating.gas => gas_charge, @Heating.electric => 0 },
>             years_here: Num = diff_years(self.start, self.moved_in),
>             tenure: Num = if self.years_here >= 3 then tenure_credit else 0,
>             premium: Premium [cite: manual:p2] =
>                 round(base + self.heat - self.tenure + sum(self.roommates as r, r.charge), 0, half_up)
>         }
>     },
>     Roommate = {
>         inputs {
>             roommate_id: Num {precision: int} where self in [1, 9],
>             smoker: Bool [checklist: "Household", statement: "{this} smokes", statement_you: "You smoke", ask: "Does {this} smoke?"]
>         },
>         step rating [cite: manual:p3] {
>             smoking: Num = if self.smoker then smoker_charge else 0,
>             charge: Num = roommate_charge + self.smoking
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check host/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
PASS host/rater.rsl (strict)
cost premium: 4 + 3*|Policy.roommates| (each list of size 1: 7, 3: 13, 10: 34)
```

## Shape

The root of a contract is an object with these keys:

| Key | Holds |
| - | - |
| `$schema` | `https://json-schema.org/draft/2020-12/schema` |
| `type` | `"object"` |
| `properties` | One schema for each root record, such as `Policy` |
| `required` | The name of each root record |
| `additionalProperties` | `false` |
| `$defs` | Each named type that a field refers to, once |
| `x-documents` | The documents that print answers, when an input has `found_on` |

A request is an object with the root record's name as its one key. `Rater.rate` also takes the root record's own fields.

Each declaration of the rater has one form in the contract:

| Declaration | Form in the contract |
| - | - |
| A record | `type: "object"` with `properties`, `required`, `additionalProperties: false` and `x-order` |
| `Num`, `Premium`, `Rate`, `Factor`, `Pct` | `type: "number"`, or `type: "integer"` for `precision: int` |
| `Bool` | `type: "boolean"` |
| `Date` | `type: "string"` with `format: "date"` and a `pattern` |
| An enum type | A `$ref` to the type's entry in `$defs`, which lists its values under `enum` |
| A `Code` type | A `$ref` to the type's entry in `$defs`, a `type: "string"` |
| A set of numbers, such as `IntValues(25000, 50000)` | The number's own keys and a `$ref` to an entry that lists the numbers under `enum` |
| `Key(T)` | The form of the table's key type, with `x-rsl-key-of` |
| `Ref(R)` | The form of the key of the list that holds `R` |
| `Option(T)` | `anyOf` of the form of `T` and `type: "null"` |
| A follow-up, `x: T when c` | The form of `Option(T)`, with `x-rsl-when` |
| A child record, `x: R` | The object of `R`, in place |
| `Option(R)` | `anyOf` of the object of `R` and `type: "null"` |
| `List(R)` | `type: "array"` with the object of `R` under `items` |
| A list of values, such as `List(Coverage)` | `type: "array"` with the form of the value under `items` |
| A union of records, `A \| B` | `oneOf` of one object for each record, each with a `tag` property |

A JSON `null` means the same as an absent field. A list that may be empty has `type: ["array", "null"]` and `default: []`. A list with a minimum length has `type: "array"`.

### Required fields

The `required` key of a record lists the fields that every request gives. These fields stay out of it:

* An `Option` field and a follow-up.
* A field with `api_default`.
* A list that may be empty.
* An input that only a suggestion reads, such as `postal_code` in the `questions` example. It takes the form of `Option`.
* A shared fact that the rater's outcomes and rules leave unread. It takes the form of `Option` too.

From the `questions` example:

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "Policy": {
    "required": ["county", "year_built", "home_business"],
    "x-order": [
      "postal_code",
      "zip_county",
      "county",
      "year_built",
      "home_business",
      "business_property",
      "customers_visit",
      "contents_limit",
      "items"
    ]
  }
}
```

## Standard keys

| Key | Comes from | Value | A host |
| - | - | - | - |
| `$schema` | Every contract | The address of the draft | Reads the contract as JSON Schema draft 2020-12 |
| `$defs`, `$ref` | An enum type, a `Code` type or a set of numbers | A pointer to the type's one entry under `$defs` | Resolves the pointer before it reads the field |
| `type` | The declared type | A JSON type name, or `["array", "null"]` | Picks the control for the answer |
| `properties` | The `inputs` of a record | One schema for each field | Reads each field's schema |
| `required` | The fields that every request gives | A list of field names | Collects each of these before it rates |
| `additionalProperties` | Every record | `false` | Sends declared fields only |
| `items` | A list | The schema of one item | Builds each item from it |
| `minItems`, `maxItems` | `where len(self) in [a, b]` | Whole numbers | Keeps the length of the list inside them |
| `anyOf` | `Option`, or a follow-up | The schema of the value, and `type: "null"` | Treats the field as optional |
| `oneOf` | A union of records | One object schema for each record | Offers the records as alternatives |
| `const` | The `tag` of a union record | The name of the record | Sends it as `tag` to name the record |
| `enum` | An enum type, or a set of numbers | The values that a request sends | Offers these values |
| `default` | `api_default`, or a list that may be empty | The value the rater uses for an absent field | Starts the answer at this value |
| `description` | `description`, or a `Code` type | Text | Shows it to a developer |
| `format` | `Date`, or `format` | `date`, `vin`, `postal-code`, `money`, `year`, `country` or `subdivision` | Picks a control and a check for the answer |
| `pattern` | `Date` | `^\d{4}-\d{2}-\d{2}$` | Sends a date as `2026-01-01` |
| `minimum`, `maximum` | `where self in [a, b]` on a number | Inclusive bounds | Keeps the answer inside them |
| `formatMinimum`, `formatMaximum` | `where self in [a, b]` on a `Date` | Inclusive dates | Keeps the date inside them |
| `multipleOf` | A whole-number precision, such as `Num {precision: 1000}` | The step | Offers amounts on that step |

An open bound publishes the nearest value inside the range. `where self in (0, 1]` at `precision: 0.01` publishes `minimum: 0.01`.

An enum member with a value of its own, such as `own = "O"`, lists that value under `enum`.

From the `questions` example:

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "contents_limit": {
    "default": 30000,
    "maximum": 100000.0,
    "minimum": 20000.0,
    "multipleOf": 1000,
    "type": "number",
    "x-ask": "How much contents coverage do you want?",
    "x-choice": true,
    "x-group": "liability",
    "x-round": "up",
    "x-shape": "limit",
    "x-unit": "USD"
  }
}
```

## Type and rule keys

| Key | Comes from | Value | A host |
| - | - | - | - |
| `x-rsl-precision` | A fractional precision, such as `Num {precision: 0.01}` | The step as text, `"0.01"` | Checks or rounds an answer to that step in decimal arithmetic |
| `x-exact-minimum`, `x-exact-maximum` | A bound that a binary float holds only approximately, such as `99999999999999999.99` | The bound as text | Compares with this text; `minimum` and `maximum` hold the nearest float inside the range |
| `x-exact-default` | A number in `api_default` that a binary float holds only approximately | The default as text | Sends the number in this text when it fills the default itself |
| `x-rsl-enum` | `Name = Enum`, an enum whose members come from `enums/Name.csv` | The name of the enum, on its `$defs` entry | Reads the members from that file of the bundle |
| `x-rsl-key-of` | `Key(T)` | The name of table `T`; several names are joined with `, ` | Treats the value as a key of that table |
| `x-rsl-pattern` | A `Code` type, in a rater that imports a package | The pattern, which the whole answer matches | Checks a typed answer against it |
| `x-rsl-refinements` | A `where` rule that JSON Schema lacks a keyword for, such as a set of values or a rule across fields | A list of `{constraint, code, message}` | Shows `constraint` as the text of the rule |
| `x-rsl-requires` | A `requires` clause, in a bundle built from RSL before 0.4 | A list of `{constraint}` | Reads each entry as the text of a rule |
| `x-rsl-when` | A follow-up, `x: T when c` | The text of `c` | Asks the field only where the condition is true |
| `x-rsl-when-reads` | A follow-up, in a rater that imports a package | The inputs of the record that `c` reads, directly or through a calculation | Asks the follow-up again after one of these answers changes |

`x-rsl-refinements` appears on a record, a union record, a field or a list. An entry has `code` and `message` when its rule has them. A request that breaks a coded rule gets a refusal with that code.

`Name [listed] = Enum` lists the members under `enum` in place of `x-rsl-enum`.

A follow-up also adds an entry to the `x-rsl-refinements` of its record. From the `questions` example:

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "business_property": {
    "anyOf": [{ "maximum": 20000.0, "minimum": 0.0, "type": "integer" }, { "type": "null" }],
    "format": "money",
    "x-ask": "What is the business equipment worth?",
    "x-group": "Business",
    "x-rsl-when": "self.home_business",
    "x-unit": "USD"
  }
}
```

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "Policy": {
    "x-rsl-refinements": [
      {
        "constraint": "where `business_property` is given when self.home_business"
      },
      {
        "constraint": "where `customers_visit` is given when self.home_business"
      }
    ]
  }
}
```

`x-rsl-when` is text for a reader. To learn which follow-ups a set of answers owes, [rate with missing answers](https://docs.canary.effectiveai.app/rsl/how-to/rate-with-missing-answers.md).

From the `household` example, a follow-up and a coded rule on a shared fact:

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "months_licensed": {
    "anyOf": [{ "maximum": 48.0, "minimum": 0.0, "type": "integer" }, { "type": "null" }],
    "x-ask": "How many months has {this} held a license?",
    "x-ask-you": "How many months have you held a license?",
    "x-refines": "age",
    "x-rsl-when": "self.age < 18",
    "x-rsl-when-reads": ["age"],
    "x-why": "New drivers are rated by the month."
  }
}
```

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "age": {
    "maximum": 99.0,
    "minimum": 14.0,
    "type": "integer",
    "x-ask": "How old is {this}?",
    "x-ask-you": "How old are you?",
    "x-fact": "example.auto@1",
    "x-rsl-refinements": [
      {
        "code": "TOO-YOUNG",
        "constraint": "where self in [16, 99]",
        "message": "Drivers are 16 or older."
      }
    ]
  }
}
```

## Question keys

| Key | Comes from | Value | A host |
| - | - | - | - |
| `x-ask` | `ask` | A template | Shows it as the question |
| `x-help` | `help` | A template | Shows it with the question |
| `x-group` | `group` | Text | Puts the question in that section |
| `x-unit` | `unit` | Text | Shows the unit beside the answer |
| `x-order` | The order of the `inputs` of a record | A list of field names | Asks the fields in this order |
| `x-labels` | `label` on an enum member, the `label` column of an enum's CSV file, or `labels` on an input | An object from each value to its words | Shows the words in place of the value |
| `x-aliases` | The `aliases` column of an enum's CSV file | An object from a value to a list of other words | Matches a typed or searched answer against them |
| `x-ask-you` | `ask_you` | A template | Uses it in place of `x-ask` for the person who answers |
| `x-why` | `why` | Text | Tells the person why this rater asks |
| `x-refines` | `x: T refines base` | The name of `base`, an input of the same record | Shows the field with `base`, and takes its answer after the answer to `base` |
| `x-closed` | `closed` | `true` | Offers only the values that the rater narrows the answer to |
| `x-none-answer` | `none_answer`, on a follow-up with the condition `self.gate` | `{label, gate}` | Offers `label` as one more answer; that answer sets the input `gate` to `false` |
| `x-short` | `short` | A label of 40 characters at most | Names the answer in a summary of the answers |
| `x-helps` | `help` on an enum member | An object from a value to its help text | Shows the text beside that value |
| `x-picker` | `picker` | One word in lower case, such as `logo` | Picks how it shows the answers of a long list |

A JSON object carries its keys in any order, so `x-order` states the order of the declarations.

On a choice, `x-group` holds a part of the coverage screen in place of a section. [Choice keys](#choice-keys) lists the parts.

From the `questions` example:

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "home_business": {
    "type": "boolean",
    "x-ask": "Do you run a business from the home?",
    "x-group": "Business",
    "x-help": "Count paid work you do at home for your own customers."
  }
}
```

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "kind": {
    "$ref": "#/$defs/ItemKind",
    "x-ask": "What kind of item is item {#}?",
    "x-labels": { "instrument": "musical instrument" }
  }
}
```

From the `host` example, the help of one value and a picker:

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "heating": {
    "$ref": "#/$defs/Heating",
    "x-ask": "How is the home heated?",
    "x-helps": { "gas": "Natural gas or propane" },
    "x-labels": { "electric": "Electric", "gas": "Gas" },
    "x-picker": "icon"
  }
}
```

From the `household` example:

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "make": {
    "anyOf": [{ "$ref": "#/$defs/Make" }, { "type": "null" }],
    "x-aliases": { "chevrolet": ["Chevy", "Chev"] },
    "x-ask": "What make is it?",
    "x-closed": true,
    "x-fact": "example.auto@1",
    "x-labels": { "chevrolet": "Chevrolet", "toyota": "Toyota" },
    "x-refines": "year"
  }
}
```

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "vin_pattern": {
    "anyOf": [{ "$ref": "#/$defs/VinPattern" }, { "type": "null" }],
    "format": "vin",
    "x-ask": "What is the VIN of {this}?",
    "x-check": "at 9 mod 11 ten X weights 8 7 6 5 4 3 2 10 0 9 8 7 6 5 4 3 2 values A1 B2 C3 D4 E5 F6 G7 H8 J1 K2 L3 M4 N5 P7 R9 S2 T3 U4 V5 W6 X7 Y8 Z9",
    "x-fact": "example.auto@1",
    "x-none-answer": { "gate": "vin_known", "label": "I don't have it" },
    "x-reduce": "1-8,'0',10",
    "x-rsl-when": "self.vin_known",
    "x-rsl-when-reads": ["vin_known"]
  }
}
```

### Templates

A template is text with placeholders in braces:

| Placeholder | Stands for |
| - | - |
| `{name}` | The answer to the input `name` of the same record |
| `{#}` | The place of the record in its list, from 1 |
| `{this}` | The name that `x-label` gives the list item |
| `{look_back}` | The start of the look-back window of the list |

`x-ask`, `x-help` and `x-label` may hold alternatives separated by `|`. A host takes the first alternative whose placeholders all have answers.

## Date keys

| Key | Comes from | Value | A host |
| - | - | - | - |
| `x-earliest` | `earliest`, on a `Date` input | A date expression | Keeps the answer on or after that date |
| `x-latest` | `latest`, on a `Date` input | A date expression | Keeps the answer on or before that date |
| `x-date-precision` | `date_precision: "month"` | `"month"` | Asks for a month, and sends the first day of that month |
| `x-priced-on` | `priced_on`, on a `Date` input | `true` | Reads the answer as the date that the rater prices for |

A date expression starts with one of three bases:

* `today`, the date of the conversation.
* The name of a `Date` input of the same record, such as `start`.
* A path from the root record, such as `Policy.start`.

An offset may follow the base: `+ 1 year`, `- 6 months` or `+ 30 days`. A base that lacks an answer sets the bound aside.

`formatMinimum` and `formatMaximum` still hold the fixed range of the rater. A host applies both, so the narrower bound decides.

From the `host` example:

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "start": {
    "format": "date",
    "formatMaximum": "2027-12-31",
    "formatMinimum": "2026-01-01",
    "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
    "type": "string",
    "x-ask": "When does the policy start?",
    "x-earliest": "today",
    "x-latest": "today + 1 year",
    "x-priced-on": true,
    "x-short": "Start"
  }
}
```

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "moved_in": {
    "format": "date",
    "formatMaximum": "2027-12-31",
    "formatMinimum": "1950-01-01",
    "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
    "type": "string",
    "x-ask": "When did you move in?",
    "x-date-precision": "month",
    "x-latest": "start"
  }
}
```

## List keys

| Key | Comes from | Value | A host |
| - | - | - | - |
| `x-keyed-by` | `keyed by f` | The name of the key field | Gives each item its own key, and names an item by it |
| `x-label` | `called` | A template over the inputs of the item and `{#}` | Names the item in questions, and fills `{this}` with the name |
| `x-complete` | `complete` | A template | Asks it before it closes the list |
| `x-you` | `you: "field = value"` | `{field, value}` | Treats the item with that value as the person who answers |
| `x-look-back-years` | `look_back: N` | A whole number of years | Fills `{look_back}` with the start of that window |
| `x-inline` | `inline` | `true` | Adds the items on the screen of the list itself |
| `x-rows` | `rows`, on an input of a list item | `true` | Asks the question of every item on one screen |
| `x-ref` | `refers: "Policy.drivers"` | The path of a list | Offers the items of that list as the answers, each by its key |
| `x-max-decline` | A coded `where len(self) in [a, N]`, in a rater that imports a package | `N` | Reads `N` as the longest list that this rater prices |
| `x-add` | `add`, on a list of records | Text, such as `Add a roommate` | Shows it on the button that adds an item |
| `x-item-name` | `item_name`, on a list of records | Text, such as `First name` | Asks each item for a name under this label, and keeps the name |

A field with `x-ref` may carry `x-labels` for the values outside the keys of the list, such as `0`.

The name that a host collects for `x-item-name` stays with the host. A request leaves it out.

From the `questions` example, the keys of the list beside its `items`:

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "items": {
    "default": [],
    "maxItems": 5,
    "type": ["array", "null"],
    "x-ask": "Which valuables do you want to schedule?",
    "x-complete": "Any other valuables?",
    "x-group": "Valuables",
    "x-help": "Add each item worth $1,000 or more.",
    "x-keyed-by": "item_id",
    "x-label": "the {kind}|item {#}"
  }
}
```

From the `host` example:

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "roommates": {
    "default": [],
    "type": ["array", "null"],
    "x-add": "Add a roommate",
    "x-ask": "Who lives with you?",
    "x-complete": "Anyone else?",
    "x-item-name": "First name",
    "x-keyed-by": "roommate_id",
    "x-label": "roommate {#}"
  }
}
```

From the `household` example, the same keys of two lists, and a field that names a driver:

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "drivers": {
    "minItems": 1,
    "type": "array",
    "x-ask": "Who drives these cars?",
    "x-complete": "Anyone else?",
    "x-fact": "example.auto@1",
    "x-group": "Drivers",
    "x-inline": true,
    "x-keyed-by": "driver_id",
    "x-label": "{relationship}|driver {#}",
    "x-you": { "field": "relationship", "value": "applicant" }
  }
}
```

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "accidents": {
    "default": [],
    "type": ["array", "null"],
    "x-ask": "Any accidents for {this} since {look_back}?",
    "x-ask-you": "Any accidents for you since {look_back}?",
    "x-fact": "example.auto@1",
    "x-keyed-by": "accident_id",
    "x-label": "accident {#}",
    "x-look-back-years": 5
  }
}
```

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "principal_driver": {
    "anyOf": [{ "maximum": 9.0, "minimum": 0.0, "type": "integer" }, { "type": "null" }],
    "x-ask": "Who drives {this} most?",
    "x-fact": "example.auto@1",
    "x-labels": { "0": "No one more than the others" },
    "x-ref": "Policy.drivers"
  }
}
```

## Choice keys

A choice is something the person buys, such as a limit or a deductible.

| Key | Comes from | Value | A host |
| - | - | - | - |
| `x-choice` | `choice` | `true` | Offers the field as a coverage option, apart from the questions |
| `default` | `api_default`, which a choice requires | A JSON value | Starts the option at this value |
| `x-shape` | `shape` | `limit`, `limit_pair`, `deductible`, `per_day`, `included`, `tier` or `term` | Picks the control for the option |
| `x-round` | The shape | `"up"` for `limit`, `limit_pair` and `per_day`; `"down"` for `deductible` | Prices an amount that this rater leaves unsold at the nearest sold amount in that direction |
| `x-code` | `coverage: "bodily_injury"` | A member of the package's enum of coverage codes | Treats choices with one code in several raters as one coverage |
| `x-steps` | `steps: "25000/50000, 100000/300000"` | A list of pairs of whole numbers | Offers these pairs |
| `x-pair` | A `limit_pair` choice, or a record choice with `steps` | The two number fields of the record, in order | Reads each pair of `x-steps` in this field order |
| `x-group` | `group`, or the shape and the record of the choice | `policy`, `liability`, `car` or `extras` | Shows the option in that part of the coverage screen |

A request that leaves a choice out buys its default. The shapes `included`, `tier` and `term` carry `x-shape` alone, and a host matches them exactly.

Every choice carries `x-group`. A `group` that names one of the four parts, in any letter case, is the group. For any other choice, the shape and the record decide:

| Shape and record | `x-group` |
| - | - |
| `term` | `policy` |
| `deductible` or `limit`, on an item of a list | `car` |
| `limit` or `limit_pair`, outside every list | `liability` |
| Any other choice | `extras` |

The `contents_limit` of the `questions` example declares `group: "Coverage"` and has the shape `limit`, so its group is `liability`.

The `contents_limit` fragment under [Standard keys](#standard-keys) is a `limit` choice. From the `household` example, a pair of limits:

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "bodily_injury": {
    "additionalProperties": false,
    "default": { "per_accident": 50000, "per_person": 25000 },
    "properties": {
      "per_accident": { "maximum": 1000000.0, "minimum": 0.0, "type": "integer" },
      "per_person": { "maximum": 1000000.0, "minimum": 0.0, "type": "integer" }
    },
    "required": ["per_person", "per_accident"],
    "type": "object",
    "x-choice": true,
    "x-code": "bodily_injury",
    "x-group": "liability",
    "x-order": ["per_person", "per_accident"],
    "x-pair": ["per_person", "per_accident"],
    "x-round": "up",
    "x-shape": "limit_pair",
    "x-steps": [
      [25000, 50000],
      [100000, 300000]
    ]
  }
}
```

## Keys for answers a host fills

| Key | Comes from | Value | A host |
| - | - | - | - |
| `x-filled-by` | `filled_by: "source(input).field"` | `{source, from, field}` | Gives the answer to `from` to its function `source`, and stores the `field` of the result |
| `x-from` | `x: T from e` | `{text, reads}`: the expression `e` and the inputs it reads | Shows the rater's suggestion once each input in `reads` has a value |
| `x-reduce` | `reduce: "1-8,'0',10"` | Positions and quoted characters | Cuts a longer typed answer down to these before it stores the answer |
| `x-check` | `check: "at 9 mod 11 …"` | The rule of a weighted check digit | Checks the full typed answer against the rule |

The rater works a suggestion out when it rates with missing answers. [Results](https://docs.canary.effectiveai.app/rsl/reference/results.md) describes where a suggestion appears.

`x-reduce` is a list of parts separated by commas. A part is a position counted from 1, a range of positions such as `1-8`, or a quoted character such as `'0'`. A host keeps those characters of the typed answer, in that order.

`x-check` has the form `at P mod M ten C weights W… values L…`:

* Each character has a value. A digit is its own value, and a letter has the value listed after `values`, such as `A1`.
* The check digit is the sum of each value times the weight at its position, modulo `M`.
* The character at position `P` equals the check digit. `C` stands for a check digit of ten.

The `vin_pattern` fragment under [Question keys](#question-keys) carries both keys.

From the `questions` example:

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "zip_county": {
    "anyOf": [{ "$ref": "#/$defs/County" }, { "type": "null" }],
    "x-filled-by": { "field": "county", "from": "postal_code", "source": "zip_lookup" }
  }
}
```

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "county": {
    "$ref": "#/$defs/County",
    "x-ask": "Which county is the home in?",
    "x-from": { "reads": ["zip_county"], "text": "self.zip_county" },
    "x-group": "Home"
  }
}
```

## Document and checklist keys

| Key | Comes from | Value | A host |
| - | - | - | - |
| `x-evidence` | `found_on: "declarations_page, registration"` | A list of document kinds | Offers to read the answer from one of these documents |
| `x-documents` | Each kind that a `found_on` names | On the root: an object from each kind to `{label, when}` | Offers a document after the input at `when` is `true` |
| `x-checklist` | `checklist: "Cars"` | The name of a checklist | Shows the input as one row of that checklist |
| `x-statement` | `statement` | A template | Shows it as the words of the row |
| `x-statement-you` | `statement_you`, beside a `statement` | A template | Uses it in place of `x-statement` for the person who answers |
| `x-attestation` | `attestation`, on a `Bool` input | `true` | Asks the input as a question of its own, outside every checklist |

In `x-documents`, `label` is the kind with spaces for its underscores. `when` is the path of the root input whose `gives` names the kind. A kind with `when` absent is on offer from the start.

From the `household` example:

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "x-documents": {
    "declarations_page": { "label": "declarations page", "when": "Policy.currently_insured" },
    "registration": { "label": "registration" }
  }
}
```

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "year": {
    "anyOf": [{ "maximum": 2100.0, "minimum": 1900.0, "type": "integer" }, { "type": "null" }],
    "format": "year",
    "x-ask": "What year is car {#}?",
    "x-evidence": ["declarations_page", "registration"],
    "x-fact": "example.auto@1"
  }
}
```

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "rideshare": {
    "type": "boolean",
    "x-ask": "Is {this} used for rideshare or delivery?",
    "x-checklist": "Cars",
    "x-fact": "example.auto@1",
    "x-statement": "Used for rideshare or delivery"
  }
}
```

From the `host` example, a row with words for the person who answers, and an attestation:

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "smoker": {
    "type": "boolean",
    "x-ask": "Does {this} smoke?",
    "x-checklist": "Household",
    "x-statement": "{this} smokes",
    "x-statement-you": "You smoke"
  }
}
```

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "complete_list": {
    "type": "boolean",
    "x-ask": "Does the list name everyone who lives with you?",
    "x-attestation": true,
    "x-rsl-refinements": [
      {
        "code": "INCOMPLETE",
        "constraint": "where self",
        "message": "List everyone who lives with you."
      }
    ]
  }
}
```

## Shared-fact keys

| Key | Comes from | Value | A host |
| - | - | - | - |
| `x-fact` | An input that an imported package declares | `name@version`, such as `example.auto@1` | Treats fields with one path and one `x-fact` in several raters as one question |

A rater that imports a package publishes these forms in its own way:

* A coded `where len(self) in [a, N]` publishes `x-max-decline` in place of `maxItems`.
* A coded `where self in [a, b]` is an entry of `x-rsl-refinements` in place of `minimum` and `maximum`.
* A shared fact that the rater narrows with `as T [code: …]` keeps the range of the package as `minimum` and `maximum`.
* A follow-up carries `x-rsl-when-reads`, and a `Code` type carries `x-rsl-pattern`.
* A shared fact that the rater leaves unread is optional, in the form of `Option`.

A rater that stands alone publishes a coded range as `minimum` and `maximum`, and a coded length as `minItems` and `maxItems`.

From the `household` example, the keys of a list with a coded maximum, and a `Code` type:

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "cars": {
    "minItems": 1,
    "type": "array",
    "x-ask": "Which cars do you want to insure?",
    "x-complete": "Any other cars?",
    "x-evidence": ["declarations_page"],
    "x-fact": "example.auto@1",
    "x-group": "Cars",
    "x-keyed-by": "car_id",
    "x-label": "the {year} {make}|car {#}",
    "x-max-decline": 4
  }
}
```

```json theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
{
  "VinPattern": {
    "description": "Code `VinPattern`",
    "type": "string",
    "x-rsl-pattern": "[0-9A-Z]{8}0[0-9A-Z]"
  }
}
```

## Related pages

* [Names](https://docs.canary.effectiveai.app/rsl/reference/names.md#annotations) lists each annotation, how to write it and where it applies.
* [Refusals](https://docs.canary.effectiveai.app/rsl/reference/refusals.md) describes the answer to a request that breaks the contract.
* [Results](https://docs.canary.effectiveai.app/rsl/reference/results.md) describes what a rater returns for a request.
* [Planner](https://docs.canary.effectiveai.app/rsl/reference/planner.md) describes the cards that the planner of `rslc` builds from a contract.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.