{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/dotsy-ai/json-schema/dotsy-ai-route-schema.json",
  "title": "Route",
  "x-generated": "2026-11-06",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/dotsy-ai-openapi.yml#/components/schemas/Route",
  "type": "object",
  "properties": {
    "path": {
      "type": "string",
      "description": "The URL path. The home page is `/`. A `:param` segment makes a pattern route."
    },
    "contentKey": {
      "type": [
        "string",
        "null"
      ],
      "description": "The content key this route serves. Null on a redirect, and on a route with no explicit key (it then serves the key derived from its path)."
    },
    "layout": {
      "type": [
        "string",
        "null"
      ],
      "description": "Layout file key, for example `layouts/main.html`."
    },
    "title": {
      "type": [
        "string",
        "null"
      ],
      "description": "Page title."
    },
    "vars": {
      "type": [
        "object",
        "null"
      ],
      "additionalProperties": {
        "type": "string"
      },
      "description": "Template variables passed to the layout."
    },
    "purpose": {
      "type": [
        "string",
        "null"
      ],
      "description": "`redirect` for a redirect route, otherwise null."
    },
    "redirectTo": {
      "type": [
        "string",
        "null"
      ],
      "description": "Redirect target, a root-relative path. Set only when `purpose` is `redirect`."
    },
    "redirectStatus": {
      "anyOf": [
        {
          "type": "number",
          "enum": [
            301
          ]
        },
        {
          "type": "number",
          "enum": [
            302
          ]
        },
        {
          "type": "number",
          "enum": [
            308
          ]
        },
        {
          "type": "null"
        }
      ],
      "description": "Redirect status code. Null means the default (301)."
    },
    "preserveQuery": {
      "type": [
        "boolean",
        "null"
      ],
      "description": "Whether a redirect appends the visitor's query string to its target. Null means the default (true)."
    },
    "navLabel": {
      "type": [
        "string",
        "null"
      ],
      "description": "Deprecated. Menu link text — use the menu API instead."
    },
    "nav": {
      "type": [
        "array",
        "null"
      ],
      "items": {
        "type": "object",
        "properties": {
          "menu": {
            "type": "string",
            "minLength": 1,
            "description": "Menu this page joins — matches a <dotsy-nav menu=\"…\"> in the layout/fragment (e.g. \"primary\", \"footer\")."
          },
          "order": {
            "type": "integer",
            "description": "Sort position among siblings in this menu, ascending. Use gaps (10, 20, 30) to leave room for inserts."
          },
          "parent": {
            "type": [
              "string",
              "null"
            ],
            "description": "Parent route path within this menu for a nested/dropdown item (e.g. \"/services\"). Omit for top level."
          }
        },
        "required": [
          "menu"
        ]
      },
      "description": "Deprecated. Menu memberships — use the menu API instead."
    },
    "experiment": {
      "type": [
        "object",
        "null"
      ],
      "properties": {
        "id": {
          "type": "string",
          "pattern": "^[a-z0-9-]+$",
          "description": "Stable experiment slug — cookie namespace; unique per site forever."
        },
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 80
        },
        "hypothesis": {
          "type": "string",
          "maxLength": 200
        },
        "status": {
          "type": "string",
          "enum": [
            "draft",
            "running",
            "ended"
          ]
        },
        "variants": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "pattern": "^[a-z0-9-]+$",
                "description": "Variant arm id — default \"a\" / \"b\" on create."
              },
              "label": {
                "type": "string",
                "maxLength": 80
              },
              "weight": {
                "type": "integer",
                "minimum": 0,
                "maximum": 100
              },
              "contentKey": {
                "type": "string",
                "minLength": 1,
                "description": "Published body key (may be routeless)."
              }
            },
            "required": [
              "id",
              "weight",
              "contentKey"
            ]
          },
          "minItems": 2,
          "maxItems": 3
        },
        "goalId": {
          "type": "string",
          "minLength": 1,
          "description": "Optional dotsy conversion goal; omit when tracking in external analytics."
        },
        "changeNote": {
          "type": "string",
          "maxLength": 120,
          "description": "One line on what the variant changes (0.56e M2). Kept here so route PATCH round-trips (weights/settings updates) don't strip it."
        },
        "handsOff": {
          "type": "boolean",
          "description": "Hands-off mode (0.56e M4): the daily sweep may auto-promote a ≥95% winner or auto-end keep-A after 30 days. Prefer setting this via start_test; kept here so route PATCH round-trips (weights/settings updates) don't strip the flag."
        },
        "startedAt": {
          "type": "string"
        },
        "endedAt": {
          "type": "string"
        }
      },
      "required": [
        "id",
        "name",
        "status",
        "variants"
      ],
      "description": "The page's A/B test, when it has one."
    },
    "noindex": {
      "type": [
        "boolean",
        "null"
      ],
      "description": "True when the page is excluded from the sitemap and marked `noindex`."
    },
    "createdBy": {
      "type": [
        "string",
        "null"
      ],
      "enum": [
        "person",
        "ai",
        "key",
        "move"
      ],
      "description": "Who made this route a redirect: a signed-in `person`, an `ai` (MCP OAuth), an API `key`, or a page `move`. Present only on redirects."
    },
    "createdById": {
      "type": [
        "string",
        "null"
      ],
      "description": "The user id when `createdBy` is `person`."
    },
    "updatedAt": {
      "type": "number",
      "description": "Last change, Unix ms."
    }
  },
  "required": [
    "path",
    "contentKey",
    "layout",
    "title",
    "vars",
    "purpose",
    "redirectTo",
    "redirectStatus",
    "preserveQuery",
    "navLabel",
    "nav",
    "updatedAt"
  ]
}